format with prettier

This commit is contained in:
Sean Yesmunt 2017-06-05 21:21:55 -07:00
parent 6f93834990
commit c8b949f020
124 changed files with 8463 additions and 7383 deletions

View file

@ -1,5 +1,5 @@
import * as types from 'constants/action_types'
import lbry from 'lbry'
import * as types from 'constants/action_types';
import lbry from 'lbry';
import {
selectUpdateUrl,
selectUpgradeDownloadPath,
@ -7,37 +7,31 @@ import {
selectUpgradeFilename,
selectPageTitle,
selectCurrentPage,
selectCurrentParams,
} from 'selectors/app'
import {
doSearch,
} from 'actions/search'
selectCurrentParams
} from 'selectors/app';
import { doSearch } from 'actions/search';
const {remote, ipcRenderer, shell} = require('electron');
const { remote, ipcRenderer, shell } = require('electron');
const path = require('path');
const app = require('electron').remote.app;
const {download} = remote.require('electron-dl');
const { download } = remote.require('electron-dl');
const fs = remote.require('fs');
const queryStringFromParams = (params) => {
return Object
.keys(params)
.map(key => `${key}=${params[key]}`)
.join('&')
}
const queryStringFromParams = params => {
return Object.keys(params).map(key => `${key}=${params[key]}`).join('&');
};
export function doNavigate(path, params = {}) {
return function(dispatch, getState) {
let url = path
if (params)
url = `${url}?${queryStringFromParams(params)}`
let url = path;
if (params) url = `${url}?${queryStringFromParams(params)}`;
dispatch(doChangePath(url))
dispatch(doChangePath(url));
const state = getState()
const pageTitle = selectPageTitle(state)
dispatch(doHistoryPush(params, pageTitle, url))
}
const state = getState();
const pageTitle = selectPageTitle(state);
dispatch(doHistoryPush(params, pageTitle, url));
};
}
export function doChangePath(path) {
@ -45,37 +39,37 @@ export function doChangePath(path) {
dispatch({
type: types.CHANGE_PATH,
data: {
path,
path
}
})
});
const state = getState()
const pageTitle = selectPageTitle(state)
window.document.title = pageTitle
window.scrollTo(0, 0)
const state = getState();
const pageTitle = selectPageTitle(state);
window.document.title = pageTitle;
window.scrollTo(0, 0);
const currentPage = selectCurrentPage(state)
const currentPage = selectCurrentPage(state);
if (currentPage === 'search') {
const params = selectCurrentParams(state)
dispatch(doSearch(params.query))
}
const params = selectCurrentParams(state);
dispatch(doSearch(params.query));
}
};
}
export function doHistoryBack() {
return function(dispatch, getState) {
history.back()
}
history.back();
};
}
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('/')
title += " - LBRY"
history.pushState(params, title, url)
}
let pathParts = window.location.pathname.split('/');
pathParts[pathParts.length - 1] = relativeUrl.replace(/^\//, '');
const url = pathParts.join('/');
title += ' - LBRY';
history.pushState(params, title, url);
};
}
export function doOpenModal(modal) {
@ -84,13 +78,13 @@ export function doOpenModal(modal) {
data: {
modal
}
}
};
}
export function doCloseModal() {
return {
type: types.CLOSE_MODAL,
}
type: types.CLOSE_MODAL
};
}
export function doUpdateDownloadProgress(percent) {
@ -99,37 +93,40 @@ export function doUpdateDownloadProgress(percent) {
data: {
percent: percent
}
}
};
}
export function doSkipUpgrade() {
return {
type: types.SKIP_UPGRADE
}
};
}
export function doStartUpgrade() {
return function(dispatch, getState) {
const state = getState()
const upgradeDownloadPath = selectUpgradeDownloadPath(state)
const state = getState();
const upgradeDownloadPath = selectUpgradeDownloadPath(state);
ipcRenderer.send('upgrade', upgradeDownloadPath)
}
ipcRenderer.send('upgrade', upgradeDownloadPath);
};
}
export function doDownloadUpgrade() {
return function(dispatch, getState) {
const state = getState()
const state = getState();
// Make a new directory within temp directory so the filename is guaranteed to be available
const dir = fs.mkdtempSync(app.getPath('temp') + require('path').sep);
const upgradeFilename = selectUpgradeFilename(state)
const upgradeFilename = selectUpgradeFilename(state);
let options = {
onProgress: (p) => dispatch(doUpdateDownloadProgress(Math.round(p * 100))),
directory: dir,
onProgress: p => dispatch(doUpdateDownloadProgress(Math.round(p * 100))),
directory: dir
};
download(remote.getCurrentWindow(), selectUpdateUrl(state), options)
.then(downloadItem => {
download(
remote.getCurrentWindow(),
selectUpdateUrl(state),
options
).then(downloadItem => {
/**
* TODO: get the download path directly from the download object. It should just be
* downloadItem.getSavePath(), but the copy on the main process is being garbage collected
@ -142,25 +139,25 @@ export function doDownloadUpgrade() {
downloadItem,
path: path.join(dir, upgradeFilename)
}
})
});
});
dispatch({
type: types.UPGRADE_DOWNLOAD_STARTED
})
});
dispatch({
type: types.OPEN_MODAL,
data: {
modal: 'downloading'
}
})
}
});
};
}
export function doCancelUpgrade() {
return function(dispatch, getState) {
const state = getState()
const upgradeDownloadItem = selectUpgradeDownloadItem(state)
const state = getState();
const upgradeDownloadItem = selectUpgradeDownloadItem(state);
if (upgradeDownloadItem) {
/*
@ -171,68 +168,68 @@ export function doCancelUpgrade() {
try {
upgradeDownloadItem.cancel();
} catch (err) {
console.error(err)
console.error(err);
// Do nothing
}
}
dispatch({ type: types.UPGRADE_CANCELLED })
}
dispatch({ type: types.UPGRADE_CANCELLED });
};
}
export function doCheckUpgradeAvailable() {
return function(dispatch, getState) {
const state = getState()
const state = getState();
lbry.getAppVersionInfo().then(({remoteVersion, upgradeAvailable}) => {
lbry.getAppVersionInfo().then(({ remoteVersion, upgradeAvailable }) => {
if (upgradeAvailable) {
dispatch({
type: types.UPDATE_VERSION,
data: {
version: remoteVersion,
version: remoteVersion
}
})
});
dispatch({
type: types.OPEN_MODAL,
data: {
modal: 'upgrade'
}
})
}
});
}
});
};
}
export function doAlertError(errorList) {
return function(dispatch, getState) {
const state = getState()
console.log('do alert error')
console.log(errorList)
const state = getState();
console.log('do alert error');
console.log(errorList);
dispatch({
type: types.OPEN_MODAL,
data: {
modal: 'error',
extraContent: errorList
}
})
}
});
};
}
export function doDaemonReady() {
return {
type: types.DAEMON_READY
}
};
}
export function doShowSnackBar(data) {
return {
type: types.SHOW_SNACKBAR,
data,
}
data
};
}
export function doRemoveSnackBarSnack() {
return {
type: types.REMOVE_SNACKBAR_SNACK,
}
type: types.REMOVE_SNACKBAR_SNACK
};
}

View file

@ -1,29 +1,27 @@
import * as types from 'constants/action_types'
import lbry from 'lbry'
import {
selectFetchingAvailability
} from 'selectors/availability'
import * as types from 'constants/action_types';
import lbry from 'lbry';
import { selectFetchingAvailability } from 'selectors/availability';
export function doFetchAvailability(uri) {
return function(dispatch, getState) {
const state = getState()
const alreadyFetching = !!selectFetchingAvailability(state)[uri]
const state = getState();
const alreadyFetching = !!selectFetchingAvailability(state)[uri];
if (!alreadyFetching) {
dispatch({
type: types.FETCH_AVAILABILITY_STARTED,
data: {uri}
})
data: { uri }
});
lbry.get_availability({uri}).then((availability) => {
lbry.get_availability({ uri }).then(availability => {
dispatch({
type: types.FETCH_AVAILABILITY_COMPLETED,
data: {
availability,
uri,
}
})
})
uri
}
});
});
}
};
}

View file

@ -1,97 +1,84 @@
import * as types from 'constants/action_types'
import lbry from 'lbry'
import lbryio from 'lbryio'
import lbryuri from 'lbryuri'
import rewards from 'rewards'
import {
selectBalance,
} from 'selectors/wallet'
import * as types from 'constants/action_types';
import lbry from 'lbry';
import lbryio from 'lbryio';
import lbryuri from 'lbryuri';
import rewards from 'rewards';
import { selectBalance } from 'selectors/wallet';
import {
selectFileInfoForUri,
selectUrisDownloading,
} from 'selectors/file_info'
import {
selectResolvingUris
} from 'selectors/content'
import {
selectCostInfoForUri,
} from 'selectors/cost_info'
import {
selectClaimsByUri,
} from 'selectors/claims'
import {
doOpenModal,
} from 'actions/app'
selectUrisDownloading
} from 'selectors/file_info';
import { selectResolvingUris } from 'selectors/content';
import { selectCostInfoForUri } from 'selectors/cost_info';
import { selectClaimsByUri } from 'selectors/claims';
import { doOpenModal } from 'actions/app';
export function doResolveUri(uri) {
return function(dispatch, getState) {
uri = lbryuri.normalize(uri);
uri = lbryuri.normalize(uri)
const state = getState()
const alreadyResolving = selectResolvingUris(state).indexOf(uri) !== -1
const state = getState();
const alreadyResolving = selectResolvingUris(state).indexOf(uri) !== -1;
if (!alreadyResolving) {
dispatch({
type: types.RESOLVE_URI_STARTED,
data: { uri }
})
});
lbry.resolve({ uri }).then((resolutionInfo) => {
const {
claim,
certificate,
} = resolutionInfo ? resolutionInfo : { claim : null, certificate: null }
lbry.resolve({ uri }).then(resolutionInfo => {
const { claim, certificate } = resolutionInfo
? resolutionInfo
: { claim: null, certificate: null };
dispatch({
type: types.RESOLVE_URI_COMPLETED,
data: {
uri,
claim,
certificate,
}
})
})
certificate
}
});
});
}
};
}
export function doCancelResolveUri(uri) {
return function(dispatch, getState) {
lbry.cancelResolve({ uri })
lbry.cancelResolve({ uri });
dispatch({
type: types.RESOLVE_URI_CANCELED,
data: { uri }
})
}
});
};
}
export function doFetchFeaturedUris() {
return function(dispatch, getState) {
const state = getState()
const state = getState();
dispatch({
type: types.FETCH_FEATURED_CONTENT_STARTED,
})
type: types.FETCH_FEATURED_CONTENT_STARTED
});
const success = ({ Categories, Uris }) => {
let featuredUris = {};
let featuredUris = {}
Categories.forEach((category) => {
Categories.forEach(category => {
if (Uris[category] && Uris[category].length) {
featuredUris[category] = Uris[category]
featuredUris[category] = Uris[category];
}
})
});
dispatch({
type: types.FETCH_FEATURED_CONTENT_COMPLETED,
data: {
categories: Categories,
uris: featuredUris,
}
})
uris: featuredUris
}
});
};
const failure = () => {
dispatch({
@ -100,25 +87,30 @@ export function doFetchFeaturedUris() {
categories: [],
uris: {}
}
})
}
});
};
lbryio.call('discover', 'list', { version: "early-access" } )
.then(success, failure)
}
lbryio
.call('discover', 'list', { version: 'early-access' })
.then(success, failure);
};
}
export function doUpdateLoadStatus(uri, outpoint) {
return function(dispatch, getState) {
const state = getState()
const state = getState();
lbry.file_list({
lbry
.file_list({
outpoint: outpoint,
full_status: true,
}).then(([fileInfo]) => {
if(!fileInfo || fileInfo.written_bytes == 0) {
full_status: true
})
.then(([fileInfo]) => {
if (!fileInfo || fileInfo.written_bytes == 0) {
// download hasn't started yet
setTimeout(() => { dispatch(doUpdateLoadStatus(uri, outpoint)) }, 250)
setTimeout(() => {
dispatch(doUpdateLoadStatus(uri, outpoint));
}, 250);
} else if (fileInfo.completed) {
// TODO this isn't going to get called if they reload the client before
// the download finished
@ -127,16 +119,13 @@ export function doUpdateLoadStatus(uri, outpoint) {
data: {
uri,
outpoint,
fileInfo,
fileInfo
}
})
});
} else {
// ready to play
const {
total_bytes,
written_bytes,
} = fileInfo
const progress = (written_bytes / total_bytes) * 100
const { total_bytes, written_bytes } = fileInfo;
const progress = written_bytes / total_bytes * 100;
dispatch({
type: types.DOWNLOADING_PROGRESSED,
@ -144,112 +133,118 @@ export function doUpdateLoadStatus(uri, outpoint) {
uri,
outpoint,
fileInfo,
progress,
progress
}
})
setTimeout(() => { dispatch(doUpdateLoadStatus(uri, outpoint)) }, 250)
}
})
});
setTimeout(() => {
dispatch(doUpdateLoadStatus(uri, outpoint));
}, 250);
}
});
};
}
export function doDownloadFile(uri, streamInfo) {
return function(dispatch, getState) {
const state = getState()
const state = getState();
lbry.file_list({ outpoint: streamInfo.outpoint, full_status: true }).then(([fileInfo]) => {
lbry
.file_list({ outpoint: streamInfo.outpoint, full_status: true })
.then(([fileInfo]) => {
dispatch({
type: types.DOWNLOADING_STARTED,
data: {
uri,
outpoint: streamInfo.outpoint,
fileInfo,
fileInfo
}
})
});
dispatch(doUpdateLoadStatus(uri, streamInfo.outpoint))
})
dispatch(doUpdateLoadStatus(uri, streamInfo.outpoint));
});
lbryio.call('file', 'view', {
lbryio
.call('file', 'view', {
uri: uri,
outpoint: streamInfo.outpoint,
claim_id: streamInfo.claim_id,
}).catch(() => {})
claim_id: streamInfo.claim_id
})
.catch(() => {});
rewards.claimEligiblePurchaseRewards()
}
rewards.claimEligiblePurchaseRewards();
};
}
export function doLoadVideo(uri) {
return function(dispatch, getState) {
const state = getState()
const state = getState();
dispatch({
type: types.LOADING_VIDEO_STARTED,
data: {
uri
}
})
});
lbry.get({ uri }).then(streamInfo => {
const timeout = streamInfo === null ||
const timeout =
streamInfo === null ||
typeof streamInfo !== 'object' ||
streamInfo.error == 'Timeout'
streamInfo.error == 'Timeout';
if(timeout) {
if (timeout) {
dispatch({
type: types.LOADING_VIDEO_FAILED,
data: { uri }
})
dispatch(doOpenModal('timedOut'))
});
dispatch(doOpenModal('timedOut'));
} else {
dispatch(doDownloadFile(uri, streamInfo))
}
})
dispatch(doDownloadFile(uri, streamInfo));
}
});
};
}
export function doPurchaseUri(uri, purchaseModalName) {
return function(dispatch, getState) {
const state = getState()
const balance = selectBalance(state)
const fileInfo = selectFileInfoForUri(state, { uri })
const downloadingByUri = selectUrisDownloading(state)
const alreadyDownloading = !!downloadingByUri[uri]
const state = getState();
const balance = selectBalance(state);
const fileInfo = selectFileInfoForUri(state, { uri });
const downloadingByUri = selectUrisDownloading(state);
const alreadyDownloading = !!downloadingByUri[uri];
// we already fully downloaded the file.
if (fileInfo && fileInfo.completed) {
// If written_bytes is false that means the user has deleted/moved the
// file manually on their file system, so we need to dispatch a
// doLoadVideo action to reconstruct the file from the blobs
if (!fileInfo.written_bytes) dispatch(doLoadVideo(uri))
if (!fileInfo.written_bytes) dispatch(doLoadVideo(uri));
return Promise.resolve()
return Promise.resolve();
}
// we are already downloading the file
if (alreadyDownloading) {
return Promise.resolve()
return Promise.resolve();
}
const costInfo = selectCostInfoForUri(state, { uri })
const { cost } = costInfo
const costInfo = selectCostInfoForUri(state, { uri });
const { cost } = costInfo;
// the file is free or we have partially downloaded it
if (cost <= 0.01 || (fileInfo && fileInfo.download_directory)) {
dispatch(doLoadVideo(uri))
return Promise.resolve()
dispatch(doLoadVideo(uri));
return Promise.resolve();
}
if (cost > balance) {
dispatch(doOpenModal('notEnoughCredits'))
dispatch(doOpenModal('notEnoughCredits'));
} else {
dispatch(doOpenModal(purchaseModalName))
dispatch(doOpenModal(purchaseModalName));
}
return Promise.resolve()
}
return Promise.resolve();
};
}
export function doFetchClaimsByChannel(uri) {
@ -257,12 +252,12 @@ export function doFetchClaimsByChannel(uri) {
dispatch({
type: types.FETCH_CHANNEL_CLAIMS_STARTED,
data: { uri }
})
});
lbry.resolve({ uri }).then((resolutionInfo) => {
const {
claims_in_channel,
} = resolutionInfo ? resolutionInfo : { claims_in_channel: [] }
lbry.resolve({ uri }).then(resolutionInfo => {
const { claims_in_channel } = resolutionInfo
? resolutionInfo
: { claims_in_channel: [] };
dispatch({
type: types.FETCH_CHANNEL_CLAIMS_COMPLETED,
@ -270,25 +265,24 @@ export function doFetchClaimsByChannel(uri) {
uri,
claims: claims_in_channel
}
})
})
}
});
});
};
}
export function doFetchClaimListMine() {
return function(dispatch, getState) {
dispatch({
type: types.FETCH_CLAIM_LIST_MINE_STARTED
})
});
lbry.claim_list_mine().then((claims) => {
lbry.claim_list_mine().then(claims => {
dispatch({
type: types.FETCH_CLAIM_LIST_MINE_COMPLETED,
data: {
claims
}
})
})
}
});
});
};
}

View file

@ -1,48 +1,40 @@
import * as types from 'constants/action_types'
import lbry from 'lbry'
import lbryio from 'lbryio'
import {
doResolveUri
} from 'actions/content'
import {
selectResolvingUris,
} from 'selectors/content'
import {
selectClaimsByUri
} from 'selectors/claims'
import {
selectSettingsIsGenerous
} from 'selectors/settings'
import * as types from 'constants/action_types';
import lbry from 'lbry';
import lbryio from 'lbryio';
import { doResolveUri } from 'actions/content';
import { selectResolvingUris } from 'selectors/content';
import { selectClaimsByUri } from 'selectors/claims';
import { selectSettingsIsGenerous } from 'selectors/settings';
export function doFetchCostInfoForUri(uri) {
return function(dispatch, getState) {
const state = getState(),
claim = selectClaimsByUri(state)[uri],
isResolving = selectResolvingUris(state).indexOf(uri) !== -1,
isGenerous = selectSettingsIsGenerous(state)
isGenerous = selectSettingsIsGenerous(state);
if (claim === null) { //claim doesn't exist, nothing to fetch a cost for
return
if (claim === null) {
//claim doesn't exist, nothing to fetch a cost for
return;
}
if (!claim) {
setTimeout(() => {
dispatch(doFetchCostInfoForUri(uri))
}, 1000)
dispatch(doFetchCostInfoForUri(uri));
}, 1000);
if (!isResolving) {
dispatch(doResolveUri(uri))
dispatch(doResolveUri(uri));
}
return
return;
}
function begin() {
dispatch({
type: types.FETCH_COST_INFO_STARTED,
data: {
uri,
uri
}
})
});
}
function resolve(costInfo) {
@ -50,28 +42,27 @@ export function doFetchCostInfoForUri(uri) {
type: types.FETCH_COST_INFO_COMPLETED,
data: {
uri,
costInfo,
costInfo
}
})
});
}
if (isGenerous && claim) {
let cost
let cost;
const fee = claim.value.stream.metadata.fee;
if (fee === undefined ) {
resolve({ cost: 0, includesData: true })
if (fee === undefined) {
resolve({ cost: 0, includesData: true });
} else if (fee.currency == 'LBC') {
resolve({ cost: fee.amount, includesData: true })
resolve({ cost: fee.amount, includesData: true });
} else {
begin()
lbryio.getExchangeRates().then(({lbc_usd}) => {
resolve({ cost: fee.amount / lbc_usd, includesData: true })
begin();
lbryio.getExchangeRates().then(({ lbc_usd }) => {
resolve({ cost: fee.amount / lbc_usd, includesData: true });
});
}
} else {
begin()
lbry.getCostInfo(uri).then(resolve)
}
begin();
lbry.getCostInfo(uri).then(resolve);
}
};
}

View file

@ -1,121 +1,113 @@
import * as types from 'constants/action_types'
import lbry from 'lbry'
import {
doFetchClaimListMine
} from 'actions/content'
import * as types from 'constants/action_types';
import lbry from 'lbry';
import { doFetchClaimListMine } from 'actions/content';
import {
selectClaimsByUri,
selectClaimListMineIsPending,
} from 'selectors/claims'
selectClaimListMineIsPending
} from 'selectors/claims';
import {
selectFileListIsPending,
selectAllFileInfos,
selectUrisLoading,
} from 'selectors/file_info'
import {
doCloseModal,
} from 'actions/app'
selectUrisLoading
} from 'selectors/file_info';
import { doCloseModal } from 'actions/app';
const {
shell,
} = require('electron')
const { shell } = require('electron');
export function doFetchFileInfo(uri) {
return function(dispatch, getState) {
const state = getState()
const claim = selectClaimsByUri(state)[uri]
const outpoint = claim ? `${claim.txid}:${claim.nout}` : null
const alreadyFetching = !!selectUrisLoading(state)[uri]
const state = getState();
const claim = selectClaimsByUri(state)[uri];
const outpoint = claim ? `${claim.txid}:${claim.nout}` : null;
const alreadyFetching = !!selectUrisLoading(state)[uri];
if (!alreadyFetching) {
dispatch({
type: types.FETCH_FILE_INFO_STARTED,
data: {
outpoint,
outpoint
}
})
lbry.file_list({outpoint: outpoint, full_status: true}).then(fileInfos => {
});
lbry
.file_list({ outpoint: outpoint, full_status: true })
.then(fileInfos => {
dispatch({
type: types.FETCH_FILE_INFO_COMPLETED,
data: {
outpoint,
fileInfo: fileInfos && fileInfos.length ? fileInfos[0] : null,
}
})
})
fileInfo: fileInfos && fileInfos.length ? fileInfos[0] : null
}
});
});
}
};
}
export function doFileList() {
return function(dispatch, getState) {
const state = getState()
const isPending = selectFileListIsPending(state)
const state = getState();
const isPending = selectFileListIsPending(state);
if (!isPending) {
dispatch({
type: types.FILE_LIST_STARTED,
})
type: types.FILE_LIST_STARTED
});
lbry.file_list().then((fileInfos) => {
lbry.file_list().then(fileInfos => {
dispatch({
type: types.FILE_LIST_COMPLETED,
data: {
fileInfos,
}
})
})
fileInfos
}
});
});
}
};
}
export function doOpenFileInShell(fileInfo) {
return function(dispatch, getState) {
shell.openItem(fileInfo.download_path)
}
shell.openItem(fileInfo.download_path);
};
}
export function doOpenFileInFolder(fileInfo) {
return function(dispatch, getState) {
shell.showItemInFolder(fileInfo.download_path)
}
shell.showItemInFolder(fileInfo.download_path);
};
}
export function doDeleteFile(outpoint, deleteFromComputer) {
return function(dispatch, getState) {
dispatch({
type: types.FILE_DELETE,
data: {
outpoint
}
})
});
lbry.file_delete({
outpoint: outpoint,
delete_target_file: deleteFromComputer,
})
delete_target_file: deleteFromComputer
});
dispatch(doCloseModal())
}
dispatch(doCloseModal());
};
}
export function doFetchFileInfosAndPublishedClaims() {
return function(dispatch, getState) {
const state = getState(),
isClaimListMinePending = selectClaimListMineIsPending(state),
isFileInfoListPending = selectFileListIsPending(state)
isFileInfoListPending = selectFileListIsPending(state);
if (isClaimListMinePending === undefined) {
dispatch(doFetchClaimListMine())
dispatch(doFetchClaimListMine());
}
if (isFileInfoListPending === undefined) {
dispatch(doFileList())
}
dispatch(doFileList());
}
};
}

View file

@ -1,36 +1,35 @@
import * as types from 'constants/action_types'
import lbry from 'lbry'
import * as types from 'constants/action_types';
import lbry from 'lbry';
import lbryio from 'lbryio';
import rewards from 'rewards'
import rewards from 'rewards';
export function doFetchRewards() {
return function(dispatch, getState) {
const state = getState()
const state = getState();
dispatch({
type: types.FETCH_REWARDS_STARTED,
})
type: types.FETCH_REWARDS_STARTED
});
lbryio.call('reward', 'list', {}).then(function(userRewards) {
dispatch({
type: types.FETCH_REWARDS_COMPLETED,
data: { userRewards }
})
});
}
});
};
}
export function doClaimReward(rewardType) {
return function(dispatch, getState) {
try {
rewards.claimReward(rewards[rewardType])
rewards.claimReward(rewards[rewardType]);
dispatch({
type: types.REWARD_CLAIMED,
data: {
reward: rewards[rewardType]
}
})
} catch(err) {
}
}
});
} catch (err) {}
};
}

View file

@ -1,54 +1,47 @@
import * as types from 'constants/action_types'
import lbryuri from 'lbryuri'
import lighthouse from 'lighthouse'
import {
doResolveUri,
} from 'actions/content'
import {
doNavigate,
doHistoryPush
} from 'actions/app'
import {
selectCurrentPage,
} from 'selectors/app'
import * as types from 'constants/action_types';
import lbryuri from 'lbryuri';
import lighthouse from 'lighthouse';
import { doResolveUri } from 'actions/content';
import { doNavigate, doHistoryPush } from 'actions/app';
import { selectCurrentPage } from 'selectors/app';
export function doSearch(query) {
return function(dispatch, getState) {
const state = getState()
const page = selectCurrentPage(state)
const state = getState();
const page = selectCurrentPage(state);
if (!query) {
return dispatch({
type: types.SEARCH_CANCELLED,
})
type: types.SEARCH_CANCELLED
});
}
dispatch({
type: types.SEARCH_STARTED,
data: { query }
})
});
if(page != 'search') {
dispatch(doNavigate('search', { query: query }))
if (page != 'search') {
dispatch(doNavigate('search', { query: query }));
} else {
lighthouse.search(query).then(results => {
results.forEach(result => {
const uri = lbryuri.build({
channelName: result.channel_name,
contentName: result.name,
claimId: result.channel_id || result.claim_id,
})
dispatch(doResolveUri(uri))
})
claimId: result.channel_id || result.claim_id
});
dispatch(doResolveUri(uri));
});
dispatch({
type: types.SEARCH_COMPLETED,
data: {
query,
results,
}
})
})
results
}
});
});
}
};
}

View file

@ -1,31 +1,31 @@
import * as types from 'constants/action_types'
import lbry from 'lbry'
import * as types from 'constants/action_types';
import lbry from 'lbry';
export function doFetchDaemonSettings() {
return function(dispatch, getState) {
lbry.settings_get().then((settings) => {
lbry.settings_get().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.settings_get().then((settings) => {
lbry.settings_set(settings).then(settings);
lbry.settings_get().then(settings => {
dispatch({
type: types.DAEMON_SETTINGS_RECEIVED,
data: {
settings
}
})
})
}
});
});
};
}

View file

@ -1,13 +1,11 @@
import * as types from 'constants/action_types'
import lbry from 'lbry'
import * as types from 'constants/action_types';
import lbry from 'lbry';
import {
selectDraftTransaction,
selectDraftTransactionAmount,
selectBalance,
} from 'selectors/wallet'
import {
doOpenModal,
} from 'actions/app'
selectBalance
} from 'selectors/wallet';
import { doOpenModal } from 'actions/app';
export function doUpdateBalance(balance) {
return {
@ -15,111 +13,115 @@ export function doUpdateBalance(balance) {
data: {
balance: balance
}
}
};
}
export function doFetchTransactions() {
return function(dispatch, getState) {
dispatch({
type: types.FETCH_TRANSACTIONS_STARTED
})
});
lbry.call('transaction_list', {}, (results) => {
lbry.call('transaction_list', {}, results => {
dispatch({
type: types.FETCH_TRANSACTIONS_COMPLETED,
data: {
transactions: results
}
})
})
}
});
});
};
}
export function doGetNewAddress() {
return function(dispatch, getState) {
dispatch({
type: types.GET_NEW_ADDRESS_STARTED
})
});
lbry.wallet_new_address().then(function(address) {
localStorage.setItem('wallet_address', address);
dispatch({
type: types.GET_NEW_ADDRESS_COMPLETED,
data: { address }
})
})
}
});
});
};
}
export function doCheckAddressIsMine(address) {
return function(dispatch, getState) {
dispatch({
type: types.CHECK_ADDRESS_IS_MINE_STARTED
})
});
lbry.checkAddressIsMine(address, (isMine) => {
if (!isMine) dispatch(doGetNewAddress())
lbry.checkAddressIsMine(address, isMine => {
if (!isMine) dispatch(doGetNewAddress());
dispatch({
type: types.CHECK_ADDRESS_IS_MINE_COMPLETED
})
})
}
});
});
};
}
export function doSendDraftTransaction() {
return function(dispatch, getState) {
const state = getState()
const draftTx = selectDraftTransaction(state)
const balance = selectBalance(state)
const amount = selectDraftTransactionAmount(state)
const state = getState();
const draftTx = selectDraftTransaction(state);
const balance = selectBalance(state);
const amount = selectDraftTransactionAmount(state);
if (balance - amount < 1) {
return dispatch(doOpenModal('insufficientBalance'))
return dispatch(doOpenModal('insufficientBalance'));
}
dispatch({
type: types.SEND_TRANSACTION_STARTED,
})
type: types.SEND_TRANSACTION_STARTED
});
const successCallback = (results) => {
if(results === true) {
const successCallback = results => {
if (results === true) {
dispatch({
type: types.SEND_TRANSACTION_COMPLETED,
})
dispatch(doOpenModal('transactionSuccessful'))
}
else {
type: types.SEND_TRANSACTION_COMPLETED
});
dispatch(doOpenModal('transactionSuccessful'));
} else {
dispatch({
type: types.SEND_TRANSACTION_FAILED,
data: { error: results }
})
dispatch(doOpenModal('transactionFailed'))
}
});
dispatch(doOpenModal('transactionFailed'));
}
};
const errorCallback = (error) => {
const errorCallback = error => {
dispatch({
type: types.SEND_TRANSACTION_FAILED,
data: { error: error.message }
})
dispatch(doOpenModal('transactionFailed'))
}
});
dispatch(doOpenModal('transactionFailed'));
};
lbry.sendToAddress(draftTx.amount, draftTx.address, successCallback, errorCallback);
}
lbry.sendToAddress(
draftTx.amount,
draftTx.address,
successCallback,
errorCallback
);
};
}
export function doSetDraftTransactionAmount(amount) {
return {
type: types.SET_DRAFT_TRANSACTION_AMOUNT,
data: { amount }
}
};
}
export function doSetDraftTransactionAddress(address) {
return {
type: types.SET_DRAFT_TRANSACTION_ADDRESS,
data: { address }
}
};
}

View file

@ -3,8 +3,14 @@ import lbry from './lbry.js';
const env = ENV;
const config = require(`./config/${env}`);
const language = lbry.getClientSetting('language') ? lbry.getClientSetting('language') : 'en';
const i18n = require('y18n')({directory: 'app/locales', updateFiles: false, locale: language});
const language = lbry.getClientSetting('language')
? lbry.getClientSetting('language')
: 'en';
const i18n = require('y18n')({
directory: 'app/locales',
updateFiles: false,
locale: language
});
const logs = [];
const app = {
env: env,
@ -16,7 +22,7 @@ const app = {
console.log(message);
logs.push(message);
}
}
};
window.__ = i18n.__;
window.__n = i18n.__n;

View file

@ -1,26 +1,19 @@
import React from 'react';
import { connect } from 'react-redux'
import { connect } from 'react-redux';
import {
selectCurrentModal,
} from 'selectors/app'
import {
doCheckUpgradeAvailable,
doAlertError,
} from 'actions/app'
import {
doUpdateBalance,
} from 'actions/wallet'
import App from './view'
import { selectCurrentModal } from 'selectors/app';
import { doCheckUpgradeAvailable, doAlertError } from 'actions/app';
import { doUpdateBalance } from 'actions/wallet';
import App from './view';
const select = (state) => ({
modal: selectCurrentModal(state),
})
const select = state => ({
modal: selectCurrentModal(state)
});
const perform = (dispatch) => ({
alertError: (errorList) => dispatch(doAlertError(errorList)),
const perform = dispatch => ({
alertError: errorList => dispatch(doAlertError(errorList)),
checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()),
updateBalance: (balance) => dispatch(doUpdateBalance(balance))
})
updateBalance: balance => dispatch(doUpdateBalance(balance))
});
export default connect(select, perform)(App)
export default connect(select, perform)(App);

View file

@ -1,33 +1,32 @@
import React from 'react'
import Router from 'component/router'
import React from 'react';
import Router from 'component/router';
import Header from 'component/header';
import ErrorModal from 'component/errorModal'
import DownloadingModal from 'component/downloadingModal'
import UpgradeModal from 'component/upgradeModal'
import lbry from 'lbry'
import {Line} from 'rc-progress'
import ErrorModal from 'component/errorModal';
import DownloadingModal from 'component/downloadingModal';
import UpgradeModal from 'component/upgradeModal';
import lbry from 'lbry';
import { Line } from 'rc-progress';
class App extends React.Component {
componentWillMount() {
document.addEventListener('unhandledError', (event) => {
document.addEventListener('unhandledError', event => {
this.props.alertError(event.detail);
});
if (!this.props.upgradeSkipped) {
this.props.checkUpgradeAvailable()
this.props.checkUpgradeAvailable();
}
lbry.balanceSubscribe((balance) => {
this.props.updateBalance(balance)
})
lbry.balanceSubscribe(balance => {
this.props.updateBalance(balance);
});
}
render() {
const {
modal,
} = this.props
const { modal } = this.props;
return <div id="window">
return (
<div id="window">
<Header />
<div id="main-content">
<Router />
@ -36,7 +35,8 @@ class App extends React.Component {
{modal == 'downloading' && <DownloadingModal />}
{modal == 'error' && <ErrorModal />}
</div>
);
}
}
export default App
export default App;

View file

@ -1,15 +1,14 @@
import React from "react";
import lbry from "../lbry.js";
import lbryio from "../lbryio.js";
import Modal from "./modal.js";
import ModalPage from "./modal-page.js";
import Link from "component/link"
import {RewardLink} from 'component/reward-link';
import {FormRow} from "../component/form.js";
import {CreditAmount, Address} from "../component/common.js";
import {getLocal, setLocal} from '../utils.js';
import rewards from '../rewards'
import React from 'react';
import lbry from '../lbry.js';
import lbryio from '../lbryio.js';
import Modal from './modal.js';
import ModalPage from './modal-page.js';
import Link from 'component/link';
import { RewardLink } from 'component/reward-link';
import { FormRow } from '../component/form.js';
import { CreditAmount, Address } from '../component/common.js';
import { getLocal, setLocal } from '../utils.js';
import rewards from '../rewards';
class SubmitEmailStage extends React.Component {
constructor(props) {
@ -24,42 +23,70 @@ class SubmitEmailStage extends React.Component {
handleEmailChanged(event) {
this.setState({
email: event.target.value,
email: event.target.value
});
}
onEmailSaved(email) {
this.props.setStage("confirm", { email: email })
this.props.setStage('confirm', { email: email });
}
handleSubmit(event) {
event.preventDefault();
this.setState({
submitting: true,
submitting: true
});
lbryio.call('user_email', 'new', {email: this.state.email}, 'post').then(() => {
lbryio.call('user_email', 'new', { email: this.state.email }, 'post').then(
() => {
this.onEmailSaved(this.state.email);
}, (error) => {
if (error.xhr && (error.xhr.status == 409 || error.message == __("This email is already in use"))) {
},
error => {
if (
error.xhr &&
(error.xhr.status == 409 ||
error.message == __('This email is already in use'))
) {
this.onEmailSaved(this.state.email);
return;
} else if (this._emailRow) {
this._emailRow.showError(error.message)
this._emailRow.showError(error.message);
}
this.setState({ submitting: false });
});
}
);
}
render() {
return (
<section>
<form onSubmit={(event) => { this.handleSubmit(event) }}>
<FormRow ref={(ref) => { this._emailRow = ref }} type="text" label={__("Email")} placeholder="scrwvwls@lbry.io"
name="email" value={this.state.email}
onChange={(event) => { this.handleEmailChanged(event) }} />
<form
onSubmit={event => {
this.handleSubmit(event);
}}
>
<FormRow
ref={ref => {
this._emailRow = ref;
}}
type="text"
label={__('Email')}
placeholder="scrwvwls@lbry.io"
name="email"
value={this.state.email}
onChange={event => {
this.handleEmailChanged(event);
}}
/>
<div className="form-row-submit">
<Link button="primary" label={__("Next")} disabled={this.state.submitting} onClick={(event) => { this.handleSubmit(event) }} />
<Link
button="primary"
label={__('Next')}
disabled={this.state.submitting}
onClick={event => {
this.handleSubmit(event);
}}
/>
</div>
</form>
</section>
@ -75,34 +102,41 @@ class ConfirmEmailStage extends React.Component {
rewardType: null,
code: '',
submitting: false,
errorMessage: null,
errorMessage: null
};
}
handleCodeChanged(event) {
this.setState({
code: event.target.value,
code: event.target.value
});
}
handleSubmit(event) {
event.preventDefault();
this.setState({
submitting: true,
submitting: true
});
const onSubmitError = (error) => {
const onSubmitError = error => {
if (this._codeRow) {
this._codeRow.showError(error.message)
this._codeRow.showError(error.message);
}
this.setState({ submitting: false });
};
lbryio.call('user_email', 'confirm', {verification_token: this.state.code, email: this.props.email}, 'post').then((userEmail) => {
lbryio
.call(
'user_email',
'confirm',
{ verification_token: this.state.code, email: this.props.email },
'post'
)
.then(userEmail => {
if (userEmail.is_verified) {
this.props.setStage("welcome")
this.props.setStage('welcome');
} else {
onSubmitError(new Error(__("Your email is still not verified."))) //shouldn't happen?
onSubmitError(new Error(__('Your email is still not verified.'))); //shouldn't happen?
}
}, onSubmitError);
}
@ -110,15 +144,46 @@ class ConfirmEmailStage extends React.Component {
render() {
return (
<section>
<form onSubmit={(event) => { this.handleSubmit(event) }}>
<FormRow label={__("Verification Code")} ref={(ref) => { this._codeRow = ref }} type="text"
name="code" placeholder="a94bXXXXXXXXXXXXXX" value={this.state.code} onChange={(event) => { this.handleCodeChanged(event) }}
helper={__("A verification code is required to access this version.")}/>
<form
onSubmit={event => {
this.handleSubmit(event);
}}
>
<FormRow
label={__('Verification Code')}
ref={ref => {
this._codeRow = ref;
}}
type="text"
name="code"
placeholder="a94bXXXXXXXXXXXXXX"
value={this.state.code}
onChange={event => {
this.handleCodeChanged(event);
}}
helper={__(
'A verification code is required to access this version.'
)}
/>
<div className="form-row-submit form-row-submit--with-footer">
<Link button="primary" label={__("Verify")} disabled={this.state.submitting} onClick={(event) => { this.handleSubmit(event)}} />
<Link
button="primary"
label={__('Verify')}
disabled={this.state.submitting}
onClick={event => {
this.handleSubmit(event);
}}
/>
</div>
<div className="form-field__helper">
{__("No code?")} <Link onClick={() => { this.props.setStage("nocode")}} label={__("Click here")} />.
{__('No code?')}
{' '}
<Link
onClick={() => {
this.props.setStage('nocode');
}}
label={__('Click here')}
/>.
</div>
</form>
</section>
@ -128,15 +193,15 @@ class ConfirmEmailStage extends React.Component {
class WelcomeStage extends React.Component {
static propTypes = {
endAuth: React.PropTypes.func,
}
endAuth: React.PropTypes.func
};
constructor(props) {
super(props);
this.state = {
hasReward: false,
rewardAmount: null,
rewardAmount: null
};
}
@ -144,59 +209,127 @@ class WelcomeStage extends React.Component {
this.setState({
hasReward: true,
rewardAmount: reward.amount
})
});
}
render() {
return (
!this.state.hasReward ?
<Modal type="custom" isOpen={true} contentLabel={__("Welcome to LBRY")} {...this.props}>
return !this.state.hasReward
? <Modal
type="custom"
isOpen={true}
contentLabel={__('Welcome to LBRY')}
{...this.props}
>
<section>
<h3 className="modal__header">{__("Welcome to LBRY.")}</h3>
<p>{__("Using LBRY is like dating a centaur. Totally normal up top, and way different underneath.")}</p>
<p>{__("Up top, LBRY is similar to popular media sites.")}</p>
<p>{__("Below, LBRY is controlled by users -- you -- via blockchain and decentralization.")}</p>
<p>{__("Thank you for making content freedom possible! Here's a nickel, kid.")}</p>
<div style={{textAlign: "center", marginBottom: "12px"}}>
<RewardLink type="new_user" button="primary" onRewardClaim={(event) => { this.onRewardClaim(event) }} onRewardFailure={() => this.props.setStage(null)} onConfirmed={() => { this.props.setStage(null) }} />
<h3 className="modal__header">{__('Welcome to LBRY.')}</h3>
<p>
{__(
'Using LBRY is like dating a centaur. Totally normal up top, and way different underneath.'
)}
</p>
<p>{__('Up top, LBRY is similar to popular media sites.')}</p>
<p>
{__(
'Below, LBRY is controlled by users -- you -- via blockchain and decentralization.'
)}
</p>
<p>
{__(
"Thank you for making content freedom possible! Here's a nickel, kid."
)}
</p>
<div style={{ textAlign: 'center', marginBottom: '12px' }}>
<RewardLink
type="new_user"
button="primary"
onRewardClaim={event => {
this.onRewardClaim(event);
}}
onRewardFailure={() => this.props.setStage(null)}
onConfirmed={() => {
this.props.setStage(null);
}}
/>
</div>
</section>
</Modal> :
<Modal type="alert" overlayClassName="modal-overlay modal-overlay--clear" isOpen={true} contentLabel={__("Welcome to LBRY")} {...this.props} onConfirmed={() => { this.props.setStage(null) }}>
<section>
<h3 className="modal__header">{__("About Your Reward")}</h3>
<p>{__("You earned a reward of ")} <CreditAmount amount={this.state.rewardAmount} label={false} /> {__("LBRY credits, or \"LBC\".")}</p>
<p>{__("This reward will show in your Wallet momentarily, probably while you are reading this message.")}</p>
<p>{__("LBC is used to compensate creators, to publish, and to have say in how the network works.")}</p>
<p>{__("No need to understand it all just yet! Try watching or downloading something next.")}</p>
<p>{__("Finally, know that LBRY is an early beta and that it earns the name.")}</p>
</section>
</Modal>
);
: <Modal
type="alert"
overlayClassName="modal-overlay modal-overlay--clear"
isOpen={true}
contentLabel={__('Welcome to LBRY')}
{...this.props}
onConfirmed={() => {
this.props.setStage(null);
}}
>
<section>
<h3 className="modal__header">{__('About Your Reward')}</h3>
<p>
{__('You earned a reward of ')}
{' '}<CreditAmount
amount={this.state.rewardAmount}
label={false}
/>
{' '}{__('LBRY credits, or "LBC".')}
</p>
<p>
{__(
'This reward will show in your Wallet momentarily, probably while you are reading this message.'
)}
</p>
<p>
{__(
'LBC is used to compensate creators, to publish, and to have say in how the network works.'
)}
</p>
<p>
{__(
'No need to understand it all just yet! Try watching or downloading something next.'
)}
</p>
<p>
{__(
'Finally, know that LBRY is an early beta and that it earns the name.'
)}
</p>
</section>
</Modal>;
}
}
const ErrorStage = (props) => {
return <section>
<p>{__("An error was encountered that we cannot continue from.")}</p>
const ErrorStage = props => {
return (
<section>
<p>{__('An error was encountered that we cannot continue from.')}</p>
<p>{__("At least we're earning the name beta.")}</p>
{ props.errorText ? <p>{__("Message:")} {props.errorText}</p> : '' }
<Link button="alt" label={__("Try Reload")} onClick={() => { window.location.reload() } } />
{props.errorText ? <p>{__('Message:')} {props.errorText}</p> : ''}
<Link
button="alt"
label={__('Try Reload')}
onClick={() => {
window.location.reload();
}}
/>
</section>
}
);
};
const PendingStage = (props) => {
return <section>
<p>{__("Preparing for first access")} <span className="busy-indicator"></span></p>
const PendingStage = props => {
return (
<section>
<p>
{__('Preparing for first access')} <span className="busy-indicator" />
</p>
</section>
}
);
};
class CodeRequiredStage extends React.Component {
constructor(props) {
super(props);
this._balanceSubscribeId = null
this._balanceSubscribeId = null;
this.state = {
balance: 0,
@ -205,14 +338,14 @@ class CodeRequiredStage extends React.Component {
}
componentWillMount() {
this._balanceSubscribeId = lbry.balanceSubscribe((balance) => {
this._balanceSubscribeId = lbry.balanceSubscribe(balance => {
this.setState({
balance: balance
});
})
});
if (!this.state.address) {
lbry.wallet_unused_address().then((address) => {
lbry.wallet_unused_address().then(address => {
setLocal('wallet_address', address);
this.setState({ address: address });
});
@ -221,7 +354,7 @@ class CodeRequiredStage extends React.Component {
componentWillUnmount() {
if (this._balanceSubscribeId) {
lbry.balanceUnsubscribe(this._balanceSubscribeId)
lbry.balanceUnsubscribe(this._balanceSubscribeId);
}
}
@ -230,27 +363,62 @@ class CodeRequiredStage extends React.Component {
return (
<div>
<section className="section-spaced">
<p>{__("Access to LBRY is restricted as we build and scale the network.")}</p>
<p>{__("There are two ways in:")}</p>
<h3>{__("Own LBRY Credits")}</h3>
<p>{__("If you own at least 1 LBC, you can get in right now.")}</p>
<p style={{ textAlign: "center"}}><Link onClick={() => { setLocal('auth_bypassed', true); this.props.setStage(null); }}
disabled={disabled} label={__("Let Me In")} button={ disabled ? "alt" : "primary" } /></p>
<p>{__("Your balance is ")}<CreditAmount amount={this.state.balance} />. {__("To increase your balance, send credits to this address:")}</p>
<p><Address address={ this.state.address ? this.state.address : __("Generating Address...") } /></p>
<p>
{__(
'Access to LBRY is restricted as we build and scale the network.'
)}
</p>
<p>{__('There are two ways in:')}</p>
<h3>{__('Own LBRY Credits')}</h3>
<p>{__('If you own at least 1 LBC, you can get in right now.')}</p>
<p style={{ textAlign: 'center' }}>
<Link
onClick={() => {
setLocal('auth_bypassed', true);
this.props.setStage(null);
}}
disabled={disabled}
label={__('Let Me In')}
button={disabled ? 'alt' : 'primary'}
/>
</p>
<p>
{__('Your balance is ')}<CreditAmount
amount={this.state.balance}
/>. {__('To increase your balance, send credits to this address:')}
</p>
<p>
<Address
address={
this.state.address
? this.state.address
: __('Generating Address...')
}
/>
</p>
<p>{__("If you don't understand how to send credits, then...")}</p>
</section>
<section>
<h3>{__("Wait For A Code")}</h3>
<p>{__("If you provide your email, you'll automatically receive a notification when the system is open.")}</p>
<p><Link onClick={() => { this.props.setStage("email"); }} label={__("Return")} /></p>
<h3>{__('Wait For A Code')}</h3>
<p>
{__(
"If you provide your email, you'll automatically receive a notification when the system is open."
)}
</p>
<p>
<Link
onClick={() => {
this.props.setStage('email');
}}
label={__('Return')}
/>
</p>
</section>
</div>
);
}
}
export class AuthOverlay extends React.Component {
constructor(props) {
super(props);
@ -262,10 +430,10 @@ export class AuthOverlay extends React.Component {
email: SubmitEmailStage,
confirm: ConfirmEmailStage,
welcome: WelcomeStage
}
};
this.state = {
stage: "pending",
stage: 'pending',
stageProps: {}
};
}
@ -274,35 +442,43 @@ export class AuthOverlay extends React.Component {
this.setState({
stage: stage,
stageProps: stageProps
})
});
}
componentWillMount() {
lbryio.authenticate().then((user) => {
lbryio
.authenticate()
.then(user => {
if (!user.has_verified_email) {
if (getLocal('auth_bypassed')) {
this.setStage(null)
this.setStage(null);
} else {
this.setStage("email", {})
this.setStage('email', {});
}
} else {
lbryio.call('reward', 'list', {}).then((userRewards) => {
lbryio.call('reward', 'list', {}).then(userRewards => {
userRewards.filter(function(reward) {
return reward.reward_type == rewards.TYPE_NEW_USER && reward.transaction_id;
}).length ?
this.setStage(null) :
this.setStage("welcome")
return (
reward.reward_type == rewards.TYPE_NEW_USER &&
reward.transaction_id
);
}).length
? this.setStage(null)
: this.setStage('welcome');
});
}
}).catch((err) => {
this.setStage("error", { errorText: err.message })
document.dispatchEvent(new CustomEvent('unhandledError', {
})
.catch(err => {
this.setStage('error', { errorText: err.message });
document.dispatchEvent(
new CustomEvent('unhandledError', {
detail: {
message: err.message,
data: err.stack
}
}));
})
);
});
}
render() {
@ -312,16 +488,30 @@ export class AuthOverlay extends React.Component {
const StageContent = this._stages[this.state.stage];
if (!StageContent) {
return <span className="empty">{__("Unknown authentication step.")}</span>
}
return (
this.state.stage != "welcome" ?
<ModalPage className="modal-page--full" isOpen={true} contentLabel={__("Authentication")}>
<h1>{__("LBRY Early Access")}</h1>
<StageContent {...this.state.stageProps} setStage={(stage, stageProps) => { this.setStage(stage, stageProps) }} />
</ModalPage> :
<StageContent setStage={(stage, stageProps) => { this.setStage(stage, stageProps) }} {...this.state.stageProps} />
<span className="empty">{__('Unknown authentication step.')}</span>
);
}
return this.state.stage != 'welcome'
? <ModalPage
className="modal-page--full"
isOpen={true}
contentLabel={__('Authentication')}
>
<h1>{__('LBRY Early Access')}</h1>
<StageContent
{...this.state.stageProps}
setStage={(stage, stageProps) => {
this.setStage(stage, stageProps);
}}
/>
</ModalPage>
: <StageContent
setStage={(stage, stageProps) => {
this.setStage(stage, stageProps);
}}
{...this.state.stageProps}
/>;
}
}

View file

@ -6,38 +6,51 @@ export class Icon extends React.Component {
static propTypes = {
icon: React.PropTypes.string.isRequired,
className: React.PropTypes.string,
fixed: React.PropTypes.bool,
}
fixed: React.PropTypes.bool
};
render() {
const {fixed, className} = this.props;
const spanClassName = ('icon ' + ('fixed' in this.props ? 'icon-fixed-width ' : '') +
this.props.icon + ' ' + (this.props.className || ''));
return <span className={spanClassName}></span>
const { fixed, className } = this.props;
const spanClassName =
'icon ' +
('fixed' in this.props ? 'icon-fixed-width ' : '') +
this.props.icon +
' ' +
(this.props.className || '');
return <span className={spanClassName} />;
}
}
export class TruncatedText extends React.Component {
static propTypes = {
lines: React.PropTypes.number,
}
lines: React.PropTypes.number
};
static defaultProps = {
lines: null
}
};
render() {
return <span className="truncated-text" style={{ WebkitLineClamp: this.props.lines }}>{this.props.children}</span>;
return (
<span
className="truncated-text"
style={{ WebkitLineClamp: this.props.lines }}
>
{this.props.children}
</span>
);
}
}
export class BusyMessage extends React.Component {
static propTypes = {
message: React.PropTypes.string,
}
message: React.PropTypes.string
};
render() {
return <span>{this.props.message} <span className="busy-indicator"></span></span>
return (
<span>{this.props.message} <span className="busy-indicator" /></span>
);
}
}
@ -54,23 +67,29 @@ export class CreditAmount extends React.Component {
isEstimate: React.PropTypes.bool,
label: React.PropTypes.bool,
showFree: React.PropTypes.bool,
look: React.PropTypes.oneOf(['indicator', 'plain']),
}
look: React.PropTypes.oneOf(['indicator', 'plain'])
};
static defaultProps = {
precision: 1,
label: true,
showFree: false,
look: 'indicator',
}
look: 'indicator'
};
render() {
const formattedAmount = lbry.formatCredits(this.props.amount, this.props.precision);
const formattedAmount = lbry.formatCredits(
this.props.amount,
this.props.precision
);
let amountText;
if (this.props.showFree && parseFloat(formattedAmount) == 0) {
amountText = __('free');
} else if (this.props.label) {
amountText = formattedAmount + ' ' + (parseFloat(formattedAmount) == 1 ? __('credit') : __('credits'));
amountText =
formattedAmount +
' ' +
(parseFloat(formattedAmount) == 1 ? __('credit') : __('credits'));
} else {
amountText = formattedAmount;
}
@ -80,19 +99,26 @@ export class CreditAmount extends React.Component {
<span>
{amountText}
</span>
{ this.props.isEstimate ? <span className="credit-amount__estimate" title={__("This is an estimate and does not include data fees")}>*</span> : null }
{this.props.isEstimate
? <span
className="credit-amount__estimate"
title={__('This is an estimate and does not include data fees')}
>
*
</span>
: null}
</span>
);
}
}
let addressStyle = {
fontFamily: '"Consolas", "Lucida Console", "Adobe Source Code Pro", monospace',
fontFamily: '"Consolas", "Lucida Console", "Adobe Source Code Pro", monospace'
};
export class Address extends React.Component {
static propTypes = {
address: React.PropTypes.string,
}
address: React.PropTypes.string
};
constructor(props) {
super(props);
@ -102,21 +128,32 @@ export class Address extends React.Component {
render() {
return (
<input className="input-copyable" type="text" ref={(input) => { this._inputElem = input; }}
onFocus={() => { this._inputElem.select(); }} style={addressStyle} readOnly="readonly" value={this.props.address}></input>
<input
className="input-copyable"
type="text"
ref={input => {
this._inputElem = input;
}}
onFocus={() => {
this._inputElem.select();
}}
style={addressStyle}
readOnly="readonly"
value={this.props.address}
/>
);
}
}
export class Thumbnail extends React.Component {
static propTypes = {
src: React.PropTypes.string,
}
src: React.PropTypes.string
};
handleError() {
if (this.state.imageUrl != this._defaultImageUri) {
this.setState({
imageUri: this._defaultImageUri,
imageUri: this._defaultImageUri
});
}
}
@ -124,12 +161,12 @@ export class Thumbnail extends React.Component {
constructor(props) {
super(props);
this._defaultImageUri = lbry.imagePath('default-thumb.svg')
this._maxLoadTime = 10000
this._isMounted = false
this._defaultImageUri = lbry.imagePath('default-thumb.svg');
this._maxLoadTime = 10000;
this._isMounted = false;
this.state = {
imageUri: this.props.src || this._defaultImageUri,
imageUri: this.props.src || this._defaultImageUri
};
}
@ -138,7 +175,7 @@ export class Thumbnail extends React.Component {
setTimeout(() => {
if (this._isMounted && !this.refs.img.complete) {
this.setState({
imageUri: this._defaultImageUri,
imageUri: this._defaultImageUri
});
}
}, this._maxLoadTime);
@ -150,8 +187,18 @@ export class Thumbnail extends React.Component {
render() {
const className = this.props.className ? this.props.className : '',
otherProps = Object.assign({}, this.props)
otherProps = Object.assign({}, this.props);
delete otherProps.className;
return <img ref="img" onError={() => { this.handleError() }} {...otherProps} className={className} src={this.state.imageUri} />
return (
<img
ref="img"
onError={() => {
this.handleError();
}}
{...otherProps}
className={className}
src={this.state.imageUri}
/>
);
}
}

View file

@ -1,25 +1,17 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
doStartUpgrade,
doCancelUpgrade,
} from 'actions/app'
import {
selectDownloadProgress,
selectDownloadComplete,
} from 'selectors/app'
import DownloadingModal from './view'
import React from 'react';
import { connect } from 'react-redux';
import { doStartUpgrade, doCancelUpgrade } from 'actions/app';
import { selectDownloadProgress, selectDownloadComplete } from 'selectors/app';
import DownloadingModal from './view';
const select = (state) => ({
const select = state => ({
downloadProgress: selectDownloadProgress(state),
downloadComplete: selectDownloadComplete(state),
})
downloadComplete: selectDownloadComplete(state)
});
const perform = (dispatch) => ({
const perform = dispatch => ({
startUpgrade: () => dispatch(doStartUpgrade()),
cancelUpgrade: () => dispatch(doCancelUpgrade())
})
});
export default connect(select, perform)(DownloadingModal)
export default connect(select, perform)(DownloadingModal);

View file

@ -1,9 +1,7 @@
import React from 'react'
import {
Modal
} from 'component/modal'
import {Line} from 'rc-progress';
import Link from 'component/link'
import React from 'react';
import { Modal } from 'component/modal';
import { Line } from 'rc-progress';
import Link from 'component/link';
class DownloadingModal extends React.Component {
render() {
@ -11,30 +9,54 @@ class DownloadingModal extends React.Component {
downloadProgress,
downloadComplete,
startUpgrade,
cancelUpgrade,
} = this.props
cancelUpgrade
} = this.props;
return (
<Modal isOpen={true} contentLabel={__("Downloading Update")} type="custom">
{__("Downloading Update")}{downloadProgress ? `: ${downloadProgress}%` : null}
<Line percent={downloadProgress ? downloadProgress : 0} strokeWidth="4"/>
{downloadComplete ? (
<div>
<Modal
isOpen={true}
contentLabel={__('Downloading Update')}
type="custom"
>
{__('Downloading Update')}
{downloadProgress ? `: ${downloadProgress}%` : null}
<Line
percent={downloadProgress ? downloadProgress : 0}
strokeWidth="4"
/>
{downloadComplete
? <div>
<br />
<p>{__("Click \"Begin Upgrade\" to start the upgrade process.")}</p>
<p>{__("The app will close, and you will be prompted to install the latest version of LBRY.")}</p>
<p>{__("After the install is complete, please reopen the app.")}</p>
<p>{__('Click "Begin Upgrade" to start the upgrade process.')}</p>
<p>
{__(
'The app will close, and you will be prompted to install the latest version of LBRY.'
)}
</p>
<p>
{__('After the install is complete, please reopen the app.')}
</p>
</div>
) : null }
: null}
<div className="modal__buttons">
{downloadComplete
? <Link button="primary" label={__("Begin Upgrade")} className="modal__button" onClick={startUpgrade} />
? <Link
button="primary"
label={__('Begin Upgrade')}
className="modal__button"
onClick={startUpgrade}
/>
: null}
<Link button="alt" label={__("Cancel")} className="modal__button" onClick={cancelUpgrade} />
<Link
button="alt"
label={__('Cancel')}
className="modal__button"
onClick={cancelUpgrade}
/>
</div>
</Modal>
)
);
}
}
export default DownloadingModal
export default DownloadingModal;

View file

@ -1,23 +1,16 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
selectCurrentModal,
selectModalExtraContent,
} from 'selectors/app'
import {
doCloseModal,
} from 'actions/app'
import ErrorModal from './view'
import React from 'react';
import { connect } from 'react-redux';
import { selectCurrentModal, selectModalExtraContent } from 'selectors/app';
import { doCloseModal } from 'actions/app';
import ErrorModal from './view';
const select = (state) => ({
const select = state => ({
modal: selectCurrentModal(state),
error: selectModalExtraContent(state),
})
error: selectModalExtraContent(state)
});
const perform = (dispatch) => ({
const perform = dispatch => ({
closeModal: () => dispatch(doCloseModal())
})
});
export default connect(select, perform)(ErrorModal)
export default connect(select, perform)(ErrorModal);

View file

@ -1,18 +1,12 @@
import React from 'react'
import lbry from 'lbry'
import {
ExpandableModal
} from 'component/modal'
import React from 'react';
import lbry from 'lbry';
import { ExpandableModal } from 'component/modal';
class ErrorModal extends React.Component {
render() {
const {
modal,
closeModal,
error
} = this.props
const { modal, closeModal, error } = this.props;
const errorObj = typeof error === "string" ? { error: error } : error
const errorObj = typeof error === 'string' ? { error: error } : error;
const error_key_labels = {
connectionString: __('API connection string'),
@ -20,35 +14,50 @@ class ErrorModal extends React.Component {
params: __('Parameters'),
code: __('Error code'),
message: __('Error message'),
data: __('Error data'),
}
data: __('Error data')
};
const errorInfoList = []
const errorInfoList = [];
for (let key of Object.keys(error)) {
let val = typeof error[key] == 'string' ? error[key] : JSON.stringify(error[key]);
let val = typeof error[key] == 'string'
? error[key]
: JSON.stringify(error[key]);
let label = error_key_labels[key];
errorInfoList.push(<li key={key}><strong>{label}</strong>: <code>{val}</code></li>);
errorInfoList.push(
<li key={key}><strong>{label}</strong>: <code>{val}</code></li>
);
}
const errorInfo = <ul className="error-modal__error-list">{errorInfoList}</ul>
const errorInfo = (
<ul className="error-modal__error-list">{errorInfoList}</ul>
);
return(
return (
<ExpandableModal
isOpen={modal == 'error'}
contentLabel={__("Error")} className="error-modal"
contentLabel={__('Error')}
className="error-modal"
overlayClassName="error-modal-overlay"
onConfirmed={closeModal}
extraContent={errorInfo}
>
<h3 className="modal__header">{__("Error")}</h3>
<h3 className="modal__header">{__('Error')}</h3>
<div className="error-modal__content">
<div><img className="error-modal__warning-symbol" src={lbry.imagePath('warning.png')} /></div>
<p>{__("We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.")}</p>
<div>
<img
className="error-modal__warning-symbol"
src={lbry.imagePath('warning.png')}
/>
</div>
<p>
{__(
"We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem."
)}
</p>
</div>
</ExpandableModal>
)
);
}
}
export default ErrorModal
export default ErrorModal;

View file

@ -1,58 +1,64 @@
import React from 'react';
const {remote} = require('electron');
const { remote } = require('electron');
class FileSelector extends React.Component {
static propTypes = {
type: React.PropTypes.oneOf(['file', 'directory']),
initPath: React.PropTypes.string,
onFileChosen: React.PropTypes.func,
}
onFileChosen: React.PropTypes.func
};
static defaultProps = {
type: 'file',
}
type: 'file'
};
componentWillMount() {
this.setState({
path: this.props.initPath || null,
path: this.props.initPath || null
});
}
handleButtonClick() {
remote.dialog.showOpenDialog({
properties: [this.props.type == 'file' ? 'openFile' : 'openDirectory'],
}, (paths) => {
if (!paths) { // User hit cancel, so do nothing
remote.dialog.showOpenDialog(
{
properties: [this.props.type == 'file' ? 'openFile' : 'openDirectory']
},
paths => {
if (!paths) {
// User hit cancel, so do nothing
return;
}
const path = paths[0];
this.setState({
path: path,
path: path
});
if (this.props.onFileChosen) {
this.props.onFileChosen(path);
}
});
}
);
}
render() {
return (
<div className="file-selector">
<button type="button" className="file-selector__choose-button" onClick={() => this.handleButtonClick()}>
{this.props.type == 'file' ?
__('Choose File') :
__('Choose Directory')}
<button
type="button"
className="file-selector__choose-button"
onClick={() => this.handleButtonClick()}
>
{this.props.type == 'file'
? __('Choose File')
: __('Choose Directory')}
</button>
{' '}
<span className="file-selector__path">
{this.state.path ?
this.state.path :
__('No File Chosen')}
{this.state.path ? this.state.path : __('No File Chosen')}
</span>
</div>
);
}
};
}
export default FileSelector;

View file

@ -1,49 +1,30 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import {
selectPlatform,
} from 'selectors/app'
import React from 'react';
import { connect } from 'react-redux';
import { selectPlatform } from 'selectors/app';
import {
makeSelectFileInfoForUri,
makeSelectDownloadingForUri,
makeSelectLoadingForUri,
} from 'selectors/file_info'
import {
makeSelectIsAvailableForUri,
} from 'selectors/availability'
import {
selectCurrentModal,
} from 'selectors/app'
import {
makeSelectCostInfoForUri,
} from 'selectors/cost_info'
import {
doCloseModal,
doOpenModal,
doHistoryBack,
} from 'actions/app'
import {
doFetchAvailability
} from 'actions/availability'
makeSelectLoadingForUri
} from 'selectors/file_info';
import { makeSelectIsAvailableForUri } from 'selectors/availability';
import { selectCurrentModal } from 'selectors/app';
import { makeSelectCostInfoForUri } from 'selectors/cost_info';
import { doCloseModal, doOpenModal, doHistoryBack } from 'actions/app';
import { doFetchAvailability } from 'actions/availability';
import {
doOpenFileInShell,
doOpenFileInFolder,
doDeleteFile,
} from 'actions/file_info'
import {
doPurchaseUri,
doLoadVideo,
} from 'actions/content'
import FileActions from './view'
doDeleteFile
} from 'actions/file_info';
import { doPurchaseUri, doLoadVideo } from 'actions/content';
import FileActions from './view';
const makeSelect = () => {
const selectFileInfoForUri = makeSelectFileInfoForUri()
const selectIsAvailableForUri = makeSelectIsAvailableForUri()
const selectDownloadingForUri = makeSelectDownloadingForUri()
const selectCostInfoForUri = makeSelectCostInfoForUri()
const selectLoadingForUri = makeSelectLoadingForUri()
const selectFileInfoForUri = makeSelectFileInfoForUri();
const selectIsAvailableForUri = makeSelectIsAvailableForUri();
const selectDownloadingForUri = makeSelectDownloadingForUri();
const selectCostInfoForUri = makeSelectCostInfoForUri();
const selectLoadingForUri = makeSelectLoadingForUri();
const select = (state, props) => ({
fileInfo: selectFileInfoForUri(state, props),
@ -52,24 +33,24 @@ const makeSelect = () => {
modal: selectCurrentModal(state),
downloading: selectDownloadingForUri(state, props),
costInfo: selectCostInfoForUri(state, props),
loading: selectLoadingForUri(state, props),
})
loading: selectLoadingForUri(state, props)
});
return select
}
return select;
};
const perform = (dispatch) => ({
checkAvailability: (uri) => dispatch(doFetchAvailability(uri)),
const perform = dispatch => ({
checkAvailability: uri => dispatch(doFetchAvailability(uri)),
closeModal: () => dispatch(doCloseModal()),
openInFolder: (fileInfo) => dispatch(doOpenFileInFolder(fileInfo)),
openInShell: (fileInfo) => dispatch(doOpenFileInShell(fileInfo)),
openInFolder: fileInfo => dispatch(doOpenFileInFolder(fileInfo)),
openInShell: fileInfo => dispatch(doOpenFileInShell(fileInfo)),
deleteFile: (fileInfo, deleteFromComputer) => {
dispatch(doHistoryBack())
dispatch(doDeleteFile(fileInfo, deleteFromComputer))
dispatch(doHistoryBack());
dispatch(doDeleteFile(fileInfo, deleteFromComputer));
},
openModal: (modal) => dispatch(doOpenModal(modal)),
startDownload: (uri) => dispatch(doPurchaseUri(uri, 'affirmPurchase')),
loadVideo: (uri) => dispatch(doLoadVideo(uri)),
})
openModal: modal => dispatch(doOpenModal(modal)),
startDownload: uri => dispatch(doPurchaseUri(uri, 'affirmPurchase')),
loadVideo: uri => dispatch(doLoadVideo(uri))
});
export default connect(makeSelect, perform)(FileActions)
export default connect(makeSelect, perform)(FileActions);

View file

@ -1,51 +1,51 @@
import React from 'react';
import {Icon,BusyMessage} from 'component/common';
import FilePrice from 'component/filePrice'
import {Modal} from 'component/modal';
import {FormField} from 'component/form';
import { Icon, BusyMessage } from 'component/common';
import FilePrice from 'component/filePrice';
import { Modal } from 'component/modal';
import { FormField } from 'component/form';
import Link from 'component/link';
import {ToolTip} from 'component/tooltip';
import {DropDownMenu, DropDownMenuItem} from 'component/menu';
import { ToolTip } from 'component/tooltip';
import { DropDownMenu, DropDownMenuItem } from 'component/menu';
class FileActions extends React.Component {
constructor(props) {
super(props)
super(props);
this.state = {
forceShowActions: false,
deleteChecked: false,
}
deleteChecked: false
};
}
componentWillMount() {
this.checkAvailability(this.props.uri)
this.checkAvailability(this.props.uri);
}
componentWillReceiveProps(nextProps) {
this.checkAvailability(nextProps.uri)
this.checkAvailability(nextProps.uri);
}
checkAvailability(uri) {
if (!this._uri || uri !== this._uri) {
this._uri = uri;
this.props.checkAvailability(uri)
this.props.checkAvailability(uri);
}
}
onShowFileActionsRowClicked() {
this.setState({
forceShowActions: true,
forceShowActions: true
});
}
handleDeleteCheckboxClicked(event) {
this.setState({
deleteChecked: event.target.checked,
})
deleteChecked: event.target.checked
});
}
onAffirmPurchase() {
this.props.closeModal()
this.props.loadVideo(this.props.uri)
this.props.closeModal();
this.props.loadVideo(this.props.uri);
}
render() {
@ -63,89 +63,160 @@ class FileActions extends React.Component {
closeModal,
startDownload,
costInfo,
loading,
} = this.props
loading
} = this.props;
const deleteChecked = this.state.deleteChecked,
metadata = fileInfo ? fileInfo.metadata : null,
openInFolderMessage = platform.startsWith('Mac') ? __('Open in Finder') : __('Open in Folder'),
openInFolderMessage = platform.startsWith('Mac')
? __('Open in Finder')
: __('Open in Folder'),
showMenu = fileInfo && Object.keys(fileInfo).length > 0,
title = metadata ? metadata.title : uri;
let content
let content;
if (loading || downloading) {
const progress = fileInfo && fileInfo.written_bytes
? fileInfo.written_bytes / fileInfo.total_bytes * 100
: 0,
label = fileInfo
? progress.toFixed(0) + __('% complete')
: __('Connecting...'),
labelWithIcon = (
<span className="button__content">
<Icon icon="icon-download" /><span>{label}</span>
</span>
);
const
progress = (fileInfo && fileInfo.written_bytes) ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0,
label = fileInfo ? progress.toFixed(0) + __('% complete') : __('Connecting...'),
labelWithIcon = <span className="button__content"><Icon icon="icon-download" /><span>{label}</span></span>;
content = <div className="faux-button-block file-actions__download-status-bar button-set-item">
<div className="faux-button-block file-actions__download-status-bar-overlay" style={{ width: progress + '%' }}>{labelWithIcon}</div>
content = (
<div className="faux-button-block file-actions__download-status-bar button-set-item">
<div
className="faux-button-block file-actions__download-status-bar-overlay"
style={{ width: progress + '%' }}
>
{labelWithIcon}
</div>
} else if (!fileInfo && isAvailable === undefined) {
content = <BusyMessage message={__("Checking availability")} />
} else if (!fileInfo && !isAvailable && !this.state.forceShowActions) {
content = <div>
<div className="button-set-item empty">{__("Content unavailable.")}</div>
<ToolTip label={__("Why?")}
body={__("The content on LBRY is hosted by its users. It appears there are no users connected that have this file at the moment.")}
className="button-set-item" />
<Link label={__("Try Anyway")} onClick={this.onShowFileActionsRowClicked.bind(this)} className="button-text button-set-item" />
{labelWithIcon}
</div>
);
} else if (!fileInfo && isAvailable === undefined) {
content = <BusyMessage message={__('Checking availability')} />;
} else if (!fileInfo && !isAvailable && !this.state.forceShowActions) {
content = (
<div>
<div className="button-set-item empty">
{__('Content unavailable.')}
</div>
<ToolTip
label={__('Why?')}
body={__(
'The content on LBRY is hosted by its users. It appears there are no users connected that have this file at the moment.'
)}
className="button-set-item"
/>
<Link
label={__('Try Anyway')}
onClick={this.onShowFileActionsRowClicked.bind(this)}
className="button-text button-set-item"
/>
</div>
);
} else if (fileInfo === null && !downloading) {
if (!costInfo) {
content = <BusyMessage message={__("Fetching cost info")} />
content = <BusyMessage message={__('Fetching cost info')} />;
} else {
content = <Link button="text" label={__("Download")} icon="icon-download" onClick={() => { startDownload(uri) } } />;
content = (
<Link
button="text"
label={__('Download')}
icon="icon-download"
onClick={() => {
startDownload(uri);
}}
/>
);
}
} else if (fileInfo && fileInfo.download_path) {
content = <Link label={__("Open")} button="text" icon="icon-folder-open" onClick={() => openInShell(fileInfo)} />;
content = (
<Link
label={__('Open')}
button="text"
icon="icon-folder-open"
onClick={() => openInShell(fileInfo)}
/>
);
} else {
console.log('handle this case of file action props?');
}
return (
<section className="file-actions">
{ content }
{ showMenu ?
<DropDownMenu>
<DropDownMenuItem key={0} onClick={() => openInFolder(fileInfo)} label={openInFolderMessage} />
<DropDownMenuItem key={1} onClick={() => openModal('confirmRemove')} label={__("Remove...")} />
</DropDownMenu> : '' }
<Modal type="confirm" isOpen={modal == 'affirmPurchase'}
contentLabel={__("Confirm Purchase")} onConfirmed={this.onAffirmPurchase.bind(this)} onAborted={closeModal}>
{__("This will purchase")} <strong>{title}</strong> {__("for")} <strong><FilePrice uri={uri} look="plain" /></strong> {__("credits")}.
{content}
{showMenu
? <DropDownMenu>
<DropDownMenuItem
key={0}
onClick={() => openInFolder(fileInfo)}
label={openInFolderMessage}
/>
<DropDownMenuItem
key={1}
onClick={() => openModal('confirmRemove')}
label={__('Remove...')}
/>
</DropDownMenu>
: ''}
<Modal
type="confirm"
isOpen={modal == 'affirmPurchase'}
contentLabel={__('Confirm Purchase')}
onConfirmed={this.onAffirmPurchase.bind(this)}
onAborted={closeModal}
>
{__('This will purchase')} <strong>{title}</strong> {__('for')}
{' '}<strong><FilePrice uri={uri} look="plain" /></strong>
{' '}{__('credits')}.
</Modal>
<Modal isOpen={modal == 'notEnoughCredits'} contentLabel={__("Not enough credits")}
onConfirmed={closeModal}>
<Modal
isOpen={modal == 'notEnoughCredits'}
contentLabel={__('Not enough credits')}
onConfirmed={closeModal}
>
{__("You don't have enough LBRY credits to pay for this stream.")}
</Modal>
<Modal isOpen={modal == 'timedOut'} contentLabel={__("Download failed")}
onConfirmed={closeModal}>
{__("LBRY was unable to download the stream")} <strong>{uri}</strong>.
<Modal
isOpen={modal == 'timedOut'}
contentLabel={__('Download failed')}
onConfirmed={closeModal}
>
{__('LBRY was unable to download the stream')} <strong>{uri}</strong>.
</Modal>
<Modal isOpen={modal == 'confirmRemove'}
contentLabel={__("Not enough credits")}
<Modal
isOpen={modal == 'confirmRemove'}
contentLabel={__('Not enough credits')}
type="confirm"
confirmButtonLabel={__("Remove")}
confirmButtonLabel={__('Remove')}
onConfirmed={() => deleteFile(fileInfo.outpoint, deleteChecked)}
onAborted={closeModal}>
<p>{__("Are you sure you'd like to remove")} <cite>{title}</cite> {__("from LBRY?")}</p>
onAborted={closeModal}
>
<p>
{__("Are you sure you'd like to remove")} <cite>{title}</cite>
{' '}{__('from LBRY?')}
</p>
<label><FormField type="checkbox" checked={deleteChecked} onClick={this.handleDeleteCheckboxClicked.bind(this)} /> {__("Delete this file from my computer")}</label>
<label>
<FormField
type="checkbox"
checked={deleteChecked}
onClick={this.handleDeleteCheckboxClicked.bind(this)}
/>
{' '}{__('Delete this file from my computer')}
</label>
</Modal>
</section>
);
}
}
export default FileActions
export default FileActions;

View file

@ -1,50 +1,37 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
doNavigate,
} from 'actions/app'
import {
doResolveUri,
doCancelResolveUri,
} from 'actions/content'
import {
selectObscureNsfw,
} from 'selectors/app'
import React from 'react';
import { connect } from 'react-redux';
import { doNavigate } from 'actions/app';
import { doResolveUri, doCancelResolveUri } from 'actions/content';
import { selectObscureNsfw } from 'selectors/app';
import {
makeSelectClaimForUri,
makeSelectMetadataForUri,
} from 'selectors/claims'
import {
makeSelectFileInfoForUri,
} from 'selectors/file_info'
import {
makeSelectIsResolvingForUri,
} from 'selectors/content'
import FileCard from './view'
makeSelectMetadataForUri
} from 'selectors/claims';
import { makeSelectFileInfoForUri } from 'selectors/file_info';
import { makeSelectIsResolvingForUri } from 'selectors/content';
import FileCard from './view';
const makeSelect = () => {
const selectClaimForUri = makeSelectClaimForUri()
const selectFileInfoForUri = makeSelectFileInfoForUri()
const selectMetadataForUri = makeSelectMetadataForUri()
const selectResolvingUri = makeSelectIsResolvingForUri()
const selectClaimForUri = makeSelectClaimForUri();
const selectFileInfoForUri = makeSelectFileInfoForUri();
const selectMetadataForUri = makeSelectMetadataForUri();
const selectResolvingUri = makeSelectIsResolvingForUri();
const select = (state, props) => ({
claim: selectClaimForUri(state, props),
fileInfo: selectFileInfoForUri(state, props),
obscureNsfw: selectObscureNsfw(state),
metadata: selectMetadataForUri(state, props),
isResolvingUri: selectResolvingUri(state, props),
})
isResolvingUri: selectResolvingUri(state, props)
});
return select
}
return select;
};
const perform = (dispatch) => ({
const perform = dispatch => ({
navigate: (path, params) => dispatch(doNavigate(path, params)),
resolveUri: (uri) => dispatch(doResolveUri(uri)),
cancelResolveUri: (uri) => dispatch(doCancelResolveUri(uri))
})
resolveUri: uri => dispatch(doResolveUri(uri)),
cancelResolveUri: uri => dispatch(doCancelResolveUri(uri))
});
export default connect(makeSelect, perform)(FileCard)
export default connect(makeSelect, perform)(FileCard);

View file

@ -2,104 +2,114 @@ import React from 'react';
import lbry from 'lbry.js';
import lbryuri from 'lbryuri.js';
import Link from 'component/link';
import {Thumbnail, TruncatedText, Icon} from 'component/common';
import FilePrice from 'component/filePrice'
import { Thumbnail, TruncatedText, Icon } from 'component/common';
import FilePrice from 'component/filePrice';
import UriIndicator from 'component/uriIndicator';
class FileCard extends React.Component {
componentWillMount() {
this.resolve(this.props)
this.resolve(this.props);
}
componentWillReceiveProps(nextProps) {
this.resolve(nextProps)
this.resolve(nextProps);
}
resolve(props) {
const {
isResolvingUri,
resolveUri,
claim,
uri,
} = props
const { isResolvingUri, resolveUri, claim, uri } = props;
if(!isResolvingUri && claim === undefined && uri) {
resolveUri(uri)
if (!isResolvingUri && claim === undefined && uri) {
resolveUri(uri);
}
}
componentWillUnmount() {
const {
isResolvingUri,
cancelResolveUri,
uri
} = this.props
const { isResolvingUri, cancelResolveUri, uri } = this.props;
if (isResolvingUri) {
cancelResolveUri(uri)
cancelResolveUri(uri);
}
}
handleMouseOver() {
this.setState({
hovered: true,
hovered: true
});
}
handleMouseOut() {
this.setState({
hovered: false,
hovered: false
});
}
render() {
const {
claim,
fileInfo,
metadata,
isResolvingUri,
navigate,
} = this.props
const { claim, fileInfo, metadata, isResolvingUri, navigate } = this.props;
const uri = lbryuri.normalize(this.props.uri);
const title = !isResolvingUri && metadata && metadata.title ? metadata.title : uri;
const title = !isResolvingUri && metadata && metadata.title
? metadata.title
: uri;
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
let description = ""
let description = '';
if (isResolvingUri) {
description = __("Loading...")
description = __('Loading...');
} else if (metadata && metadata.description) {
description = metadata.description
description = metadata.description;
} else if (claim === null) {
description = __("This address contains no content.")
description = __('This address contains no content.');
}
return (
<section className={ 'card card--small card--link ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver.bind(this)} onMouseLeave={this.handleMouseOut.bind(this)}>
<section
className={
'card card--small card--link ' +
(obscureNsfw ? 'card--obscured ' : '')
}
onMouseEnter={this.handleMouseOver.bind(this)}
onMouseLeave={this.handleMouseOut.bind(this)}
>
<div className="card__inner">
<Link onClick={() => navigate('/show', { uri })} className="card__link">
<Link
onClick={() => navigate('/show', { uri })}
className="card__link"
>
<div className="card__title-identity">
<h5 title={title}><TruncatedText lines={1}>{title}</TruncatedText></h5>
<h5 title={title}>
<TruncatedText lines={1}>{title}</TruncatedText>
</h5>
<div className="card__subtitle">
<span style={{float: "right"}}>
<span style={{ float: 'right' }}>
<FilePrice uri={uri} />
{ fileInfo ? <span>{' '}<Icon fixed icon="icon-folder" /></span> : '' }
{fileInfo
? <span>{' '}<Icon fixed icon="icon-folder" /></span>
: ''}
</span>
<UriIndicator uri={uri} />
</div>
</div>
{metadata && metadata.thumbnail &&
<div className="card__media" style={{ backgroundImage: "url('" + metadata.thumbnail + "')" }}></div>
}
{metadata &&
metadata.thumbnail &&
<div
className="card__media"
style={{ backgroundImage: "url('" + metadata.thumbnail + "')" }}
/>}
<div className="card__content card__subtext card__subtext--two-lines">
<TruncatedText lines={2}>{description}</TruncatedText>
</div>
</Link>
{obscureNsfw && this.state.hovered
? <div className='card-overlay'>
? <div className="card-overlay">
<p>
{__("This content is Not Safe For Work. To view adult content, please change your")} <Link className="button-text" onClick={() => navigate('settings')} label={__("Settings")} />.
{__(
'This content is Not Safe For Work. To view adult content, please change your'
)}
{' '}<Link
className="button-text"
onClick={() => navigate('settings')}
label={__('Settings')}
/>.
</p>
</div>
: null}
@ -109,4 +119,4 @@ class FileCard extends React.Component {
}
}
export default FileCard
export default FileCard;

View file

@ -1,13 +1,9 @@
import React from 'react'
import {
connect
} from 'react-redux'
import FileList from './view'
import React from 'react';
import { connect } from 'react-redux';
import FileList from './view';
const select = (state) => ({
})
const select = state => ({});
const perform = (dispatch) => ({
})
const perform = dispatch => ({});
export default connect(select, perform)(FileList)
export default connect(select, perform)(FileList);

View file

@ -2,19 +2,19 @@ import React from 'react';
import lbry from 'lbry.js';
import lbryuri from 'lbryuri.js';
import Link from 'component/link';
import {FormField} from 'component/form.js';
import { FormField } from 'component/form.js';
import FileTile from 'component/fileTile';
import rewards from 'rewards.js';
import lbryio from 'lbryio.js';
import {BusyMessage, Thumbnail} from 'component/common.js';
import { BusyMessage, Thumbnail } from 'component/common.js';
class FileList extends React.Component {
constructor(props) {
super(props)
super(props);
this.state = {
sortBy: 'date',
}
sortBy: 'date'
};
this._sortFunctions = {
date: function(fileInfos) {
@ -22,8 +22,12 @@ class FileList extends React.Component {
},
title: function(fileInfos) {
return fileInfos.slice().sort(function(fileInfo1, fileInfo2) {
const title1 = fileInfo1.metadata ? fileInfo1.metadata.stream.metadata.title.toLowerCase() : fileInfo1.name;
const title2 = fileInfo2.metadata ? fileInfo2.metadata.stream.metadata.title.toLowerCase() : fileInfo2.name;
const title1 = fileInfo1.metadata
? fileInfo1.metadata.stream.metadata.title.toLowerCase()
: fileInfo1.name;
const title2 = fileInfo2.metadata
? fileInfo2.metadata.stream.metadata.title.toLowerCase()
: fileInfo2.name;
if (title1 < title2) {
return -1;
} else if (title1 > title2) {
@ -31,10 +35,12 @@ class FileList extends React.Component {
} else {
return 0;
}
})
});
},
filename: function(fileInfos) {
return fileInfos.slice().sort(function({file_name: fileName1}, {file_name: fileName2}) {
return fileInfos
.slice()
.sort(function({ file_name: fileName1 }, { file_name: fileName2 }) {
const fileName1Lower = fileName1.toLowerCase();
const fileName2Lower = fileName2.toLowerCase();
if (fileName1Lower < fileName2Lower) {
@ -44,50 +50,51 @@ class FileList extends React.Component {
} else {
return 0;
}
})
},
});
}
};
}
handleSortChanged(event) {
this.setState({
sortBy: event.target.value,
})
sortBy: event.target.value
});
}
render() {
const {
handleSortChanged,
fetching,
fileInfos,
} = this.props
const {
sortBy,
} = this.state
const content = []
const { handleSortChanged, fetching, fileInfos } = this.props;
const { sortBy } = this.state;
const content = [];
this._sortFunctions[sortBy](fileInfos).forEach(fileInfo => {
const uri = lbryuri.build({
contentName: fileInfo.name,
channelName: fileInfo.channel_name,
})
content.push(<FileTile key={uri} uri={uri} hidePrice={true} showEmpty={this.props.fileTileShowEmpty} />)
})
channelName: fileInfo.channel_name
});
content.push(
<FileTile
key={uri}
uri={uri}
hidePrice={true}
showEmpty={this.props.fileTileShowEmpty}
/>
);
});
return (
<section className="file-list__header">
{ fetching && <span className="busy-indicator"/> }
<span className='sort-section'>
{__("Sort by")} { ' ' }
{fetching && <span className="busy-indicator" />}
<span className="sort-section">
{__('Sort by')} {' '}
<FormField type="select" onChange={this.handleSortChanged.bind(this)}>
<option value="date">{__("Date")}</option>
<option value="title">{__("Title")}</option>
<option value="filename">{__("File name")}</option>
<option value="date">{__('Date')}</option>
<option value="title">{__('Title')}</option>
<option value="filename">{__('File name')}</option>
</FormField>
</span>
{content}
</section>
)
);
}
}
export default FileList
export default FileList;

View file

@ -1,29 +1,23 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import {
doSearch,
} from 'actions/search'
import React from 'react';
import { connect } from 'react-redux';
import { doSearch } from 'actions/search';
import {
selectIsSearching,
selectCurrentSearchResults,
selectSearchQuery,
} from 'selectors/search'
import {
doNavigate,
} from 'actions/app'
import FileListSearch from './view'
selectSearchQuery
} from 'selectors/search';
import { doNavigate } from 'actions/app';
import FileListSearch from './view';
const select = (state) => ({
const select = state => ({
isSearching: selectIsSearching(state),
query: selectSearchQuery(state),
results: selectCurrentSearchResults(state)
})
});
const perform = (dispatch) => ({
navigate: (path) => dispatch(doNavigate(path)),
search: (search) => dispatch(doSearch(search))
})
const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)),
search: search => dispatch(doSearch(search))
});
export default connect(select, perform)(FileListSearch)
export default connect(select, perform)(FileListSearch);

View file

@ -3,74 +3,74 @@ import lbry from 'lbry';
import lbryio from 'lbryio';
import lbryuri from 'lbryuri';
import lighthouse from 'lighthouse';
import FileTile from 'component/fileTile'
import Link from 'component/link'
import {ToolTip} from 'component/tooltip.js';
import {BusyMessage} from 'component/common.js';
import FileTile from 'component/fileTile';
import Link from 'component/link';
import { ToolTip } from 'component/tooltip.js';
import { BusyMessage } from 'component/common.js';
const SearchNoResults = (props) => {
const {
navigate,
query,
} = props
const SearchNoResults = props => {
const { navigate, query } = props;
return <section>
return (
<section>
<span className="empty">
{__("No one has checked anything in for %s yet."), query} { ' ' }
<Link label={__("Be the first")} onClick={() => navigate('/publish')} />
{(__('No one has checked anything in for %s yet.'), query)} {' '}
<Link label={__('Be the first')} onClick={() => navigate('/publish')} />
</span>
</section>;
}
</section>
);
};
const FileListSearchResults = (props) => {
const {
results,
} = props
const FileListSearchResults = props => {
const { results } = props;
const rows = [],
seenNames = {}; //fix this when the search API returns claim IDs
for (let {name, claim, claim_id, channel_name, channel_id, txid, nout} of results) {
for (let {
name,
claim,
claim_id,
channel_name,
channel_id,
txid,
nout
} of results) {
const uri = lbryuri.build({
channelName: channel_name,
contentName: name,
claimId: channel_id || claim_id,
claimId: channel_id || claim_id
});
rows.push(
<FileTile key={uri} uri={uri} />
);
rows.push(<FileTile key={uri} uri={uri} />);
}
return (
<div>{rows}</div>
);
}
return <div>{rows}</div>;
};
class FileListSearch extends React.Component{
class FileListSearch extends React.Component {
componentWillMount() {
this.props.search(this.props.query)
this.props.search(this.props.query);
}
render() {
const {
isSearching,
results
} = this.props
const { isSearching, results } = this.props;
return (
<div>
{isSearching && !results &&
<BusyMessage message={__("Looking up the Dewey Decimals")} />}
{isSearching &&
!results &&
<BusyMessage message={__('Looking up the Dewey Decimals')} />}
{isSearching && results &&
<BusyMessage message={__("Refreshing the Dewey Decimals")} />}
{isSearching &&
results &&
<BusyMessage message={__('Refreshing the Dewey Decimals')} />}
{(results && !!results.length) ?
<FileListSearchResults {...this.props} /> :
<SearchNoResults {...this.props} />}
{results && !!results.length
? <FileListSearchResults {...this.props} />
: <SearchNoResults {...this.props} />}
</div>
)
);
}
}
export default FileListSearch
export default FileListSearch;

View file

@ -1,31 +1,27 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import {
doFetchCostInfoForUri,
} from 'actions/cost_info'
import React from 'react';
import { connect } from 'react-redux';
import { doFetchCostInfoForUri } from 'actions/cost_info';
import {
makeSelectCostInfoForUri,
makeSelectFetchingCostInfoForUri,
} from 'selectors/cost_info'
import FilePrice from './view'
makeSelectFetchingCostInfoForUri
} from 'selectors/cost_info';
import FilePrice from './view';
const makeSelect = () => {
const selectCostInfoForUri = makeSelectCostInfoForUri()
const selectFetchingCostInfoForUri = makeSelectFetchingCostInfoForUri()
const selectCostInfoForUri = makeSelectCostInfoForUri();
const selectFetchingCostInfoForUri = makeSelectFetchingCostInfoForUri();
const select = (state, props) => ({
costInfo: selectCostInfoForUri(state, props),
fetching: selectFetchingCostInfoForUri(state, props),
})
fetching: selectFetchingCostInfoForUri(state, props)
});
return select
}
return select;
};
const perform = (dispatch) => ({
fetchCostInfo: (uri) => dispatch(doFetchCostInfoForUri(uri)),
const perform = dispatch => ({
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri))
// cancelFetchCostInfo: (uri) => dispatch(doCancelFetchCostInfoForUri(uri))
})
});
export default connect(makeSelect, perform)(FilePrice)
export default connect(makeSelect, perform)(FilePrice);

View file

@ -1,44 +1,43 @@
import React from 'react'
import {
CreditAmount,
} from 'component/common'
import React from 'react';
import { CreditAmount } from 'component/common';
class FilePrice extends React.Component{
class FilePrice extends React.Component {
componentWillMount() {
this.fetchCost(this.props)
this.fetchCost(this.props);
}
componentWillReceiveProps(nextProps) {
this.fetchCost(nextProps)
this.fetchCost(nextProps);
}
fetchCost(props) {
const {
costInfo,
fetchCostInfo,
uri,
fetching,
} = props
const { costInfo, fetchCostInfo, uri, fetching } = props;
if (costInfo === undefined && !fetching) {
fetchCostInfo(uri)
fetchCostInfo(uri);
}
}
render() {
const {
costInfo,
look = 'indicator',
} = this.props
const { costInfo, look = 'indicator' } = this.props;
const isEstimate = costInfo ? !costInfo.includesData : null
const isEstimate = costInfo ? !costInfo.includesData : null;
if (!costInfo) {
return <span className={`credit-amount credit-amount--${look}`}>???</span>;
return (
<span className={`credit-amount credit-amount--${look}`}>???</span>
);
}
return <CreditAmount label={false} amount={costInfo.cost} isEstimate={isEstimate} showFree={true} />
return (
<CreditAmount
label={false}
amount={costInfo.cost}
isEstimate={isEstimate}
showFree={true}
/>
);
}
}
export default FilePrice
export default FilePrice;

View file

@ -1,48 +1,36 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
doNavigate,
} from 'actions/app'
import {
doResolveUri,
} from 'actions/content'
import React from 'react';
import { connect } from 'react-redux';
import { doNavigate } from 'actions/app';
import { doResolveUri } from 'actions/content';
import {
makeSelectClaimForUri,
makeSelectMetadataForUri,
} from 'selectors/claims'
import {
makeSelectFileInfoForUri,
} from 'selectors/file_info'
import {
selectObscureNsfw,
} from 'selectors/app'
import {
makeSelectIsResolvingForUri,
} from 'selectors/content'
import FileTile from './view'
makeSelectMetadataForUri
} from 'selectors/claims';
import { makeSelectFileInfoForUri } from 'selectors/file_info';
import { selectObscureNsfw } from 'selectors/app';
import { makeSelectIsResolvingForUri } from 'selectors/content';
import FileTile from './view';
const makeSelect = () => {
const selectClaimForUri = makeSelectClaimForUri()
const selectFileInfoForUri = makeSelectFileInfoForUri()
const selectMetadataForUri = makeSelectMetadataForUri()
const selectResolvingUri = makeSelectIsResolvingForUri()
const selectClaimForUri = makeSelectClaimForUri();
const selectFileInfoForUri = makeSelectFileInfoForUri();
const selectMetadataForUri = makeSelectMetadataForUri();
const selectResolvingUri = makeSelectIsResolvingForUri();
const select = (state, props) => ({
claim: selectClaimForUri(state, props),
fileInfo: selectFileInfoForUri(state, props),
obscureNsfw: selectObscureNsfw(state),
metadata: selectMetadataForUri(state, props),
isResolvingUri: selectResolvingUri(state, props),
})
isResolvingUri: selectResolvingUri(state, props)
});
return select
}
return select;
};
const perform = (dispatch) => ({
const perform = dispatch => ({
navigate: (path, params) => dispatch(doNavigate(path, params)),
resolveUri: (uri) => dispatch(doResolveUri(uri)),
})
resolveUri: uri => dispatch(doResolveUri(uri))
});
export default connect(makeSelect, perform)(FileTile)
export default connect(makeSelect, perform)(FileTile);

View file

@ -3,38 +3,37 @@ import lbry from 'lbry.js';
import lbryuri from 'lbryuri.js';
import Link from 'component/link';
import FileActions from 'component/fileActions';
import {Thumbnail, TruncatedText,} from 'component/common.js';
import FilePrice from 'component/filePrice'
import { Thumbnail, TruncatedText } from 'component/common.js';
import FilePrice from 'component/filePrice';
import UriIndicator from 'component/uriIndicator';
class FileTile extends React.Component {
static SHOW_EMPTY_PUBLISH = "publish"
static SHOW_EMPTY_PENDING = "pending"
static SHOW_EMPTY_PUBLISH = 'publish';
static SHOW_EMPTY_PENDING = 'pending';
constructor(props) {
super(props)
super(props);
this.state = {
showNsfwHelp: false,
}
showNsfwHelp: false
};
}
componentDidMount() {
const {
isResolvingUri,
resolveUri,
claim,
uri,
} = this.props
const { isResolvingUri, resolveUri, claim, uri } = this.props;
if(!isResolvingUri && !claim && uri) {
resolveUri(uri)
if (!isResolvingUri && !claim && uri) {
resolveUri(uri);
}
}
handleMouseOver() {
if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) {
if (
this.props.obscureNsfw &&
this.props.metadata &&
this.props.metadata.nsfw
) {
this.setState({
showNsfwHelp: true,
showNsfwHelp: true
});
}
}
@ -42,7 +41,7 @@ class FileTile extends React.Component {
handleMouseOut() {
if (this.state.showNsfwHelp) {
this.setState({
showNsfwHelp: false,
showNsfwHelp: false
});
}
}
@ -54,41 +53,62 @@ class FileTile extends React.Component {
isResolvingUri,
showEmpty,
navigate,
hidePrice,
} = this.props
hidePrice
} = this.props;
const uri = lbryuri.normalize(this.props.uri);
const isClaimed = !!claim;
const isClaimable = lbryuri.isClaimable(uri)
const title = isClaimed && metadata && metadata.title ? metadata.title : uri;
const isClaimable = lbryuri.isClaimable(uri);
const title = isClaimed && metadata && metadata.title
? metadata.title
: uri;
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
let onClick = () => navigate('/show', { uri })
let onClick = () => navigate('/show', { uri });
let description = ""
let description = '';
if (isClaimed) {
description = metadata && metadata.description
description = metadata && metadata.description;
} else if (isResolvingUri) {
description = __("Loading...")
description = __('Loading...');
} else if (showEmpty === FileTile.SHOW_EMPTY_PUBLISH) {
onClick = () => navigate('/publish', { })
description = <span className="empty">
{__("This location is unused.")} { ' ' }
{ isClaimable && <span className="button-text">{__("Put something here!")}</span> }
onClick = () => navigate('/publish', {});
description = (
<span className="empty">
{__('This location is unused.')} {' '}
{isClaimable &&
<span className="button-text">{__('Put something here!')}</span>}
</span>
);
} else if (showEmpty === FileTile.SHOW_EMPTY_PENDING) {
description = <span className="empty">{__("This file is pending confirmation.")}</span>
description = (
<span className="empty">
{__('This file is pending confirmation.')}
</span>
);
}
return (
<section className={ 'file-tile card ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver.bind(this)} onMouseLeave={this.handleMouseOut.bind(this)}>
<section
className={'file-tile card ' + (obscureNsfw ? 'card--obscured ' : '')}
onMouseEnter={this.handleMouseOver.bind(this)}
onMouseLeave={this.handleMouseOut.bind(this)}
>
<Link onClick={onClick} className="card__link">
<div className={"card__inner file-tile__row"}>
<div className="card__media"
style={{ backgroundImage: "url('" + (metadata && metadata.thumbnail ? metadata.thumbnail : lbry.imagePath('default-thumb.svg')) + "')" }}>
</div>
<div className={'card__inner file-tile__row'}>
<div
className="card__media"
style={{
backgroundImage:
"url('" +
(metadata && metadata.thumbnail
? metadata.thumbnail
: lbry.imagePath('default-thumb.svg')) +
"')"
}}
/>
<div className="file-tile__content">
<div className="card__title-primary">
{ !hidePrice ? <FilePrice uri={this.props.uri} /> : null}
{!hidePrice ? <FilePrice uri={this.props.uri} /> : null}
<div className="meta">{uri}</div>
<h3><TruncatedText lines={1}>{title}</TruncatedText></h3>
</div>
@ -101,9 +121,16 @@ class FileTile extends React.Component {
</div>
</Link>
{this.state.showNsfwHelp
? <div className='card-overlay'>
? <div className="card-overlay">
<p>
{__("This content is Not Safe For Work. To view adult content, please change your")} <Link className="button-text" onClick={() => navigate('/settings')} label={__("Settings")} />.
{__(
'This content is Not Safe For Work. To view adult content, please change your'
)}
{' '}<Link
className="button-text"
onClick={() => navigate('/settings')}
label={__('Settings')}
/>.
</p>
</div>
: null}
@ -112,4 +139,4 @@ class FileTile extends React.Component {
}
}
export default FileTile
export default FileTile;

View file

@ -1,13 +1,13 @@
import React from 'react';
import FileSelector from './file-selector.js';
import {Icon} from './common.js';
import { Icon } from './common.js';
var formFieldCounter = 0,
formFieldFileSelectorTypes = ['file', 'directory'],
formFieldNestedLabelTypes = ['radio', 'checkbox'];
function formFieldId() {
return "form-field-" + (++formFieldCounter);
return 'form-field-' + ++formFieldCounter;
}
export class FormField extends React.Component {
@ -16,7 +16,7 @@ export class FormField extends React.Component {
prefix: React.PropTypes.string,
postfix: React.PropTypes.string,
hasError: React.PropTypes.bool
}
};
constructor(props) {
super(props);
@ -27,7 +27,7 @@ export class FormField extends React.Component {
this.state = {
isError: null,
errorMessage: null,
errorMessage: null
};
}
@ -59,8 +59,9 @@ export class FormField extends React.Component {
handleFileChosen(path) {
this.refs.field.value = path;
if (this.props.onChange) { // Updating inputs programmatically doesn't generate an event, so we have to make our own
const event = new Event('change', {bubbles: true})
if (this.props.onChange) {
// Updating inputs programmatically doesn't generate an event, so we have to make our own
const event = new Event('change', { bubbles: true });
this.refs.field.dispatchEvent(event); // This alone won't generate a React event, but we use it to attach the field as a target
this.props.onChange(event);
}
@ -69,7 +70,7 @@ export class FormField extends React.Component {
showError(text) {
this.setState({
isError: true,
errorMessage: text,
errorMessage: text
});
}
@ -92,9 +93,12 @@ export class FormField extends React.Component {
render() {
// Pass all unhandled props to the field element
const otherProps = Object.assign({}, this.props),
isError = this.state.isError !== null ? this.state.isError : this.props.hasError,
isError = this.state.isError !== null
? this.state.isError
: this.props.hasError,
elementId = this.props.id ? this.props.id : formFieldId(),
renderElementInsideLabel = this.props.label && formFieldNestedLabelTypes.includes(this.props.type);
renderElementInsideLabel =
this.props.label && formFieldNestedLabelTypes.includes(this.props.type);
delete otherProps.type;
delete otherProps.label;
@ -103,35 +107,71 @@ export class FormField extends React.Component {
delete otherProps.postfix;
delete otherProps.prefix;
const element = <this._element id={elementId} type={this._type} name={this.props.name} ref="field" placeholder={this.props.placeholder}
className={'form-field__input form-field__input-' + this.props.type + ' ' + (this.props.className || '') + (isError ? 'form-field__input--error' : '')}
{...otherProps}>
const element = (
<this._element
id={elementId}
type={this._type}
name={this.props.name}
ref="field"
placeholder={this.props.placeholder}
className={
'form-field__input form-field__input-' +
this.props.type +
' ' +
(this.props.className || '') +
(isError ? 'form-field__input--error' : '')
}
{...otherProps}
>
{this.props.children}
</this._element>;
</this._element>
);
return <div className={"form-field form-field--" + this.props.type}>
{ this.props.prefix ? <span className="form-field__prefix">{this.props.prefix}</span> : '' }
{ renderElementInsideLabel ?
<label htmlFor={elementId} className={"form-field__label " + (isError ? 'form-field__label--error' : '')}>
return (
<div className={'form-field form-field--' + this.props.type}>
{this.props.prefix
? <span className="form-field__prefix">{this.props.prefix}</span>
: ''}
{renderElementInsideLabel
? <label
htmlFor={elementId}
className={
'form-field__label ' +
(isError ? 'form-field__label--error' : '')
}
>
{element}
{this.props.label}
</label> :
element }
{ formFieldFileSelectorTypes.includes(this.props.type) ?
<FileSelector type={this.props.type} onFileChosen={this.handleFileChosen.bind(this)}
{... this.props.defaultValue ? {initPath: this.props.defaultValue} : {}} /> :
null }
{ this.props.postfix ? <span className="form-field__postfix">{this.props.postfix}</span> : '' }
{ isError && this.state.errorMessage ? <div className="form-field__error">{this.state.errorMessage}</div> : '' }
</label>
: element}
{formFieldFileSelectorTypes.includes(this.props.type)
? <FileSelector
type={this.props.type}
onFileChosen={this.handleFileChosen.bind(this)}
{...(this.props.defaultValue
? { initPath: this.props.defaultValue }
: {})}
/>
: null}
{this.props.postfix
? <span className="form-field__postfix">{this.props.postfix}</span>
: ''}
{isError && this.state.errorMessage
? <div className="form-field__error">{this.state.errorMessage}</div>
: ''}
</div>
);
}
}
export class FormRow extends React.Component {
static propTypes = {
label: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.element]),
label: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.element
])
// helper: React.PropTypes.html,
}
};
constructor(props) {
super(props);
@ -140,14 +180,14 @@ export class FormRow extends React.Component {
this.state = {
isError: false,
errorMessage: null,
errorMessage: null
};
}
showError(text) {
this.setState({
isError: true,
errorMessage: text,
errorMessage: text
});
}
@ -177,23 +217,43 @@ export class FormRow extends React.Component {
render() {
const fieldProps = Object.assign({}, this.props),
elementId = formFieldId(),
renderLabelInFormField = formFieldNestedLabelTypes.includes(this.props.type);
renderLabelInFormField = formFieldNestedLabelTypes.includes(
this.props.type
);
if (!renderLabelInFormField) {
delete fieldProps.label;
}
delete fieldProps.helper;
return <div className="form-row">
{ this.props.label && !renderLabelInFormField ?
<div className={"form-row__label-row " + (this.props.labelPrefix ? "form-row__label-row--prefix" : "") }>
<label htmlFor={elementId} className={"form-field__label " + (this.state.isError ? 'form-field__label--error' : '')}>
return (
<div className="form-row">
{this.props.label && !renderLabelInFormField
? <div
className={
'form-row__label-row ' +
(this.props.labelPrefix ? 'form-row__label-row--prefix' : '')
}
>
<label
htmlFor={elementId}
className={
'form-field__label ' +
(this.state.isError ? 'form-field__label--error' : '')
}
>
{this.props.label}
</label>
</div> : '' }
<FormField ref="field" hasError={this.state.isError} {...fieldProps} />
{ !this.state.isError && this.props.helper ? <div className="form-field__helper">{this.props.helper}</div> : '' }
{ this.state.isError ? <div className="form-field__error">{this.state.errorMessage}</div> : '' }
</div>
: ''}
<FormField ref="field" hasError={this.state.isError} {...fieldProps} />
{!this.state.isError && this.props.helper
? <div className="form-field__helper">{this.props.helper}</div>
: ''}
{this.state.isError
? <div className="form-field__error">{this.state.errorMessage}</div>
: ''}
</div>
);
}
}

View file

@ -1,25 +1,18 @@
import React from 'react'
import lbry from 'lbry'
import {
connect
} from 'react-redux'
import {
selectBalance
} from 'selectors/wallet'
import {
doNavigate,
doHistoryBack,
} from 'actions/app'
import Header from './view'
import React from 'react';
import lbry from 'lbry';
import { connect } from 'react-redux';
import { selectBalance } from 'selectors/wallet';
import { doNavigate, doHistoryBack } from 'actions/app';
import Header from './view';
const select = (state) => ({
const select = state => ({
balance: lbry.formatCredits(selectBalance(state), 1),
publish: __("Publish"),
})
publish: __('Publish')
});
const perform = (dispatch) => ({
navigate: (path) => dispatch(doNavigate(path)),
back: () => dispatch(doHistoryBack()),
})
const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)),
back: () => dispatch(doHistoryBack())
});
export default connect(select, perform)(Header)
export default connect(select, perform)(Header);

View file

@ -2,37 +2,56 @@ import React from 'react';
import Link from 'component/link';
import WunderBar from 'component/wunderbar';
export const Header = (props) => {
const {
balance,
back,
navigate,
publish,
} = props
export const Header = props => {
const { balance, back, navigate, publish } = props;
return <header id="header">
return (
<header id="header">
<div className="header__item">
<Link onClick={back} button="alt button--flat" icon="icon-arrow-left" />
</div>
<div className="header__item">
<Link onClick={() => navigate('/discover')} button="alt button--flat" icon="icon-home" />
<Link
onClick={() => navigate('/discover')}
button="alt button--flat"
icon="icon-home"
/>
</div>
<div className="header__item header__item--wunderbar">
<WunderBar />
</div>
<div className="header__item">
<Link onClick={() => navigate('/wallet')} button="text" icon="icon-bank" label={balance} ></Link>
<Link
onClick={() => navigate('/wallet')}
button="text"
icon="icon-bank"
label={balance}
/>
</div>
<div className="header__item">
<Link onClick={() => navigate('/publish')} button="primary button--flat" icon="icon-upload" label={publish} />
<Link
onClick={() => navigate('/publish')}
button="primary button--flat"
icon="icon-upload"
label={publish}
/>
</div>
<div className="header__item">
<Link onClick={() => navigate('/downloaded')} button="alt button--flat" icon="icon-folder" />
<Link
onClick={() => navigate('/downloaded')}
button="alt button--flat"
icon="icon-folder"
/>
</div>
<div className="header__item">
<Link onClick={() => navigate('/settings')} button="alt button--flat" icon="icon-gear" />
<Link
onClick={() => navigate('/settings')}
button="alt button--flat"
icon="icon-gear"
/>
</div>
</header>
}
);
};
export default Header;

View file

@ -1,7 +1,5 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import Link from './view'
import React from 'react';
import { connect } from 'react-redux';
import Link from './view';
export default connect(null, null)(Link)
export default connect(null, null)(Link);

View file

@ -1,7 +1,7 @@
import React from 'react';
import {Icon} from 'component/common.js';
import { Icon } from 'component/common.js';
const Link = (props) => {
const Link = props => {
const {
href,
title,
@ -13,35 +13,41 @@ const Link = (props) => {
button,
hidden,
disabled,
children,
} = props
children
} = props;
const className = (props.className || '') +
const className =
(props.className || '') +
(!props.className && !props.button ? 'button-text' : '') + // Non-button links get the same look as text buttons
(props.button ? ' button-block button-' + props.button + ' button-set-item' : '') +
(props.button
? ' button-block button-' + props.button + ' button-set-item'
: '') +
(props.disabled ? ' disabled' : '');
let content;
if (children) {
content = children
content = children;
} else {
content = (
<span {... 'button' in props ? {className: 'button__content'} : {}}>
<span {...('button' in props ? { className: 'button__content' } : {})}>
{'icon' in props ? <Icon icon={icon} fixed={true} /> : null}
{label ? <span className="link-label">{label}</span> : null}
{'badge' in props ? <span className="badge">{badge}</span> : null}
</span>
)
);
}
return (
<a className={className} href={href || 'javascript:;'} title={title}
<a
className={className}
href={href || 'javascript:;'}
title={title}
onClick={onClick}
{... 'style' in props ? {style: style} : {}}>
{...('style' in props ? { style: style } : {})}
>
{content}
</a>
);
}
};
export default Link
export default Link;

View file

@ -1,14 +1,14 @@
import React from 'react';
import lbry from '../lbry.js';
import {BusyMessage, Icon} from './common.js';
import Link from 'component/link'
import { BusyMessage, Icon } from './common.js';
import Link from 'component/link';
class LoadScreen extends React.Component {
static propTypes = {
message: React.PropTypes.string.isRequired,
details: React.PropTypes.string,
isWarning: React.PropTypes.bool,
}
isWarning: React.PropTypes.bool
};
constructor(props) {
super(props);
@ -16,31 +16,39 @@ class LoadScreen extends React.Component {
this.state = {
message: null,
details: null,
isLagging: false,
isLagging: false
};
}
static defaultProps = {
isWarning: false,
}
isWarning: false
};
render() {
const imgSrc = lbry.imagePath('lbry-white-485x160.png');
return (
<div className="load-screen">
<img src={imgSrc} alt="LBRY"/>
<img src={imgSrc} alt="LBRY" />
<div className="load-screen__message">
<h3>
{!this.props.isWarning ?
<BusyMessage message={this.props.message} /> :
<span><Icon icon="icon-warning" />{' ' + this.props.message}</span> }
{!this.props.isWarning
? <BusyMessage message={this.props.message} />
: <span>
<Icon icon="icon-warning" />{' ' + this.props.message}
</span>}
</h3>
<span className={'load-screen__details ' + (this.props.isWarning ? 'load-screen__details--warning' : '')}>{this.props.details}</span>
<span
className={
'load-screen__details ' +
(this.props.isWarning ? 'load-screen__details--warning' : '')
}
>
{this.props.details}
</span>
</div>
</div>
);
}
}
export default LoadScreen;

View file

@ -1,5 +1,5 @@
import React from 'react';
import {Icon} from './common.js';
import { Icon } from './common.js';
import Link from 'component/link';
export class DropDownMenuItem extends React.Component {
@ -7,19 +7,23 @@ export class DropDownMenuItem extends React.Component {
href: React.PropTypes.string,
label: React.PropTypes.string,
icon: React.PropTypes.string,
onClick: React.PropTypes.func,
}
onClick: React.PropTypes.func
};
static defaultProps = {
iconPosition: 'left',
}
iconPosition: 'left'
};
render() {
var icon = (this.props.icon ? <Icon icon={this.props.icon} fixed /> : null);
var icon = this.props.icon ? <Icon icon={this.props.icon} fixed /> : null;
return (
<a className="menu__menu-item" onClick={this.props.onClick}
href={this.props.href || 'javascript:'} label={this.props.label}>
<a
className="menu__menu-item"
onClick={this.props.onClick}
href={this.props.href || 'javascript:'}
label={this.props.label}
>
{this.props.iconPosition == 'left' ? icon : null}
{this.props.label}
{this.props.iconPosition == 'left' ? null : icon}
@ -36,7 +40,7 @@ export class DropDownMenu extends React.Component {
this._menuDiv = null;
this.state = {
menuOpen: false,
menuOpen: false
};
}
@ -48,7 +52,7 @@ export class DropDownMenu extends React.Component {
handleMenuIconClick(e) {
this.setState({
menuOpen: !this.state.menuOpen,
menuOpen: !this.state.menuOpen
});
if (!this.state.menuOpen && !this._isWindowClickBound) {
this._isWindowClickBound = true;
@ -61,13 +65,15 @@ export class DropDownMenu extends React.Component {
handleMenuClick(e) {
// Event bubbles up to the menu after a link is clicked
this.setState({
menuOpen: false,
menuOpen: false
});
}
handleWindowClick(e) {
if (this.state.menuOpen &&
(!this._menuDiv || !this._menuDiv.contains(e.target))) {
if (
this.state.menuOpen &&
(!this._menuDiv || !this._menuDiv.contains(e.target))
) {
this.setState({
menuOpen: false
});
@ -81,9 +87,22 @@ export class DropDownMenu extends React.Component {
}
return (
<div className="menu-container">
<Link ref={(span) => this._menuButton = span} button="text" icon="icon-ellipsis-v" onClick={(event) => { this.handleMenuIconClick(event) }} />
<Link
ref={span => (this._menuButton = span)}
button="text"
icon="icon-ellipsis-v"
onClick={event => {
this.handleMenuIconClick(event);
}}
/>
{this.state.menuOpen
? <div ref={(div) => this._menuDiv = div} className="menu" onClick={(event) => { this.handleMenuClick(event) }}>
? <div
ref={div => (this._menuDiv = div)}
className="menu"
onClick={event => {
this.handleMenuClick(event);
}}
>
{this.props.children}
</div>
: null}

View file

@ -4,9 +4,12 @@ import ReactModal from 'react-modal';
export class ModalPage extends React.Component {
render() {
return (
<ReactModal onCloseRequested={this.props.onAborted || this.props.onConfirmed} {...this.props}
<ReactModal
onCloseRequested={this.props.onAborted || this.props.onConfirmed}
{...this.props}
className={(this.props.className || '') + ' modal-page'}
overlayClassName="modal-overlay">
overlayClassName="modal-overlay"
>
<div className="modal-page__content">
{this.props.children}
</div>
@ -15,4 +18,4 @@ export class ModalPage extends React.Component {
}
}
export default ModalPage
export default ModalPage;

View file

@ -1,7 +1,7 @@
import React from 'react';
import ReactModal from 'react-modal';
import Link from 'component/link';
import app from '../app.js'
import app from '../app.js';
export class Modal extends React.Component {
static propTypes = {
@ -12,8 +12,8 @@ export class Modal extends React.Component {
confirmButtonLabel: React.PropTypes.string,
abortButtonLabel: React.PropTypes.string,
confirmButtonDisabled: React.PropTypes.bool,
abortButtonDisabled: React.PropTypes.bool,
}
abortButtonDisabled: React.PropTypes.bool
};
static defaultProps = {
type: 'alert',
@ -21,23 +21,42 @@ export class Modal extends React.Component {
confirmButtonLabel: app.i18n.__('OK'),
abortButtonLabel: app.i18n.__('Cancel'),
confirmButtonDisabled: false,
abortButtonDisabled: false,
}
abortButtonDisabled: false
};
render() {
return (
<ReactModal onCloseRequested={this.props.onAborted || this.props.onConfirmed} {...this.props}
<ReactModal
onCloseRequested={this.props.onAborted || this.props.onConfirmed}
{...this.props}
className={(this.props.className || '') + ' modal'}
overlayClassName={![null, undefined, ""].includes(this.props.overlayClassName) ? this.props.overlayClassName : 'modal-overlay'}>
overlayClassName={
![null, undefined, ''].includes(this.props.overlayClassName)
? this.props.overlayClassName
: 'modal-overlay'
}
>
<div>
{this.props.children}
</div>
{this.props.type == 'custom' // custom modals define their own buttons
? null
: <div className="modal__buttons">
<Link button="primary" label={this.props.confirmButtonLabel} className="modal__button" disabled={this.props.confirmButtonDisabled} onClick={this.props.onConfirmed} />
<Link
button="primary"
label={this.props.confirmButtonLabel}
className="modal__button"
disabled={this.props.confirmButtonDisabled}
onClick={this.props.onConfirmed}
/>
{this.props.type == 'confirm'
? <Link button="alt" label={this.props.abortButtonLabel} className="modal__button" disabled={this.props.abortButtonDisabled} onClick={this.props.onAborted} />
? <Link
button="alt"
label={this.props.abortButtonLabel}
className="modal__button"
disabled={this.props.abortButtonDisabled}
onClick={this.props.onAborted}
/>
: null}
</div>}
</ReactModal>
@ -48,40 +67,53 @@ export class Modal extends React.Component {
export class ExpandableModal extends React.Component {
static propTypes = {
expandButtonLabel: React.PropTypes.string,
extraContent: React.PropTypes.element,
}
extraContent: React.PropTypes.element
};
static defaultProps = {
confirmButtonLabel: app.i18n.__('OK'),
expandButtonLabel: app.i18n.__('Show More...'),
hideButtonLabel: app.i18n.__('Show Less'),
}
hideButtonLabel: app.i18n.__('Show Less')
};
constructor(props) {
super(props);
this.state = {
expanded: false,
}
expanded: false
};
}
toggleExpanded() {
this.setState({
expanded: !this.state.expanded,
expanded: !this.state.expanded
});
}
render() {
return (
<Modal type="custom" {... this.props}>
<Modal type="custom" {...this.props}>
{this.props.children}
{this.state.expanded
? this.props.extraContent
: null}
{this.state.expanded ? this.props.extraContent : null}
<div className="modal__buttons">
<Link button="primary" label={this.props.confirmButtonLabel} className="modal__button" onClick={this.props.onConfirmed} />
<Link button="alt" label={!this.state.expanded ? this.props.expandButtonLabel : this.props.hideButtonLabel}
className="modal__button" onClick={() => { this.toggleExpanded() }} />
<Link
button="primary"
label={this.props.confirmButtonLabel}
className="modal__button"
onClick={this.props.onConfirmed}
/>
<Link
button="alt"
label={
!this.state.expanded
? this.props.expandButtonLabel
: this.props.hideButtonLabel
}
className="modal__button"
onClick={() => {
this.toggleExpanded();
}}
/>
</div>
</Modal>
);

View file

@ -2,16 +2,22 @@ import React from 'react';
export class Notice extends React.Component {
static propTypes = {
isError: React.PropTypes.bool,
}
isError: React.PropTypes.bool
};
static defaultProps = {
isError: false,
}
isError: false
};
render() {
return (
<section className={'notice ' + (this.props.isError ? 'notice--error ' : '') + (this.props.className || '')}>
<section
className={
'notice ' +
(this.props.isError ? 'notice--error ' : '') +
(this.props.className || '')
}
>
{this.props.children}
</section>
);

View file

@ -1,9 +1,9 @@
import React from 'react';
import lbry from 'lbry'
import {Icon} from 'component/common';
import lbry from 'lbry';
import { Icon } from 'component/common';
import Modal from 'component/modal';
import rewards from 'rewards';
import Link from 'component/link'
import Link from 'component/link';
export class RewardLink extends React.Component {
static propTypes = {
@ -11,7 +11,7 @@ export class RewardLink extends React.Component {
claimed: React.PropTypes.bool,
onRewardClaim: React.PropTypes.func,
onRewardFailure: React.PropTypes.func
}
};
constructor(props) {
super(props);
@ -24,16 +24,16 @@ export class RewardLink extends React.Component {
}
refreshClaimable() {
switch(this.props.type) {
switch (this.props.type) {
case 'new_user':
this.setState({ claimable: true });
return;
case 'first_publish':
lbry.claim_list_mine().then((list) => {
lbry.claim_list_mine().then(list => {
this.setState({
claimable: list.length > 0
})
});
});
return;
}
@ -46,42 +46,60 @@ export class RewardLink extends React.Component {
claimReward() {
this.setState({
pending: true
})
});
rewards.claimReward(this.props.type).then((reward) => {
rewards
.claimReward(this.props.type)
.then(reward => {
this.setState({
pending: false,
errorMessage: null
})
});
if (this.props.onRewardClaim) {
this.props.onRewardClaim(reward);
}
}).catch((error) => {
})
.catch(error => {
this.setState({
errorMessage: error.message,
pending: false
})
})
});
});
}
clearError() {
if (this.props.onRewardFailure) {
this.props.onRewardFailure()
this.props.onRewardFailure();
}
this.setState({
errorMessage: null
})
});
}
render() {
return (
<div className="reward-link">
{this.props.claimed
? <span><Icon icon="icon-check" /> {__("Reward claimed.")}</span>
: <Link button={this.props.button ? this.props.button : 'alt'} disabled={this.state.pending || !this.state.claimable }
label={ this.state.pending ? __("Claiming...") : __("Claim Reward")} onClick={() => { this.claimReward() }} />}
{this.state.errorMessage ?
<Modal isOpen={true} contentLabel={__("Reward Claim Error")} className="error-modal" onConfirmed={() => { this.clearError() }}>
? <span><Icon icon="icon-check" /> {__('Reward claimed.')}</span>
: <Link
button={this.props.button ? this.props.button : 'alt'}
disabled={this.state.pending || !this.state.claimable}
label={
this.state.pending ? __('Claiming...') : __('Claim Reward')
}
onClick={() => {
this.claimReward();
}}
/>}
{this.state.errorMessage
? <Modal
isOpen={true}
contentLabel={__('Reward Claim Error')}
className="error-modal"
onConfirmed={() => {
this.clearError();
}}
>
{this.state.errorMessage}
</Modal>
: ''}

View file

@ -1,14 +1,11 @@
import React from 'react';
import { connect } from 'react-redux';
import Router from './view.jsx';
import {
selectCurrentPage,
selectCurrentParams,
} from 'selectors/app.js';
import { selectCurrentPage, selectCurrentParams } from 'selectors/app.js';
const select = (state) => ({
const select = state => ({
params: selectCurrentParams(state),
currentPage: selectCurrentPage(state)
})
});
export default connect(select, null)(Router);

View file

@ -4,48 +4,44 @@ import HelpPage from 'page/help';
import ReportPage from 'page/report.js';
import StartPage from 'page/start.js';
import WalletPage from 'page/wallet';
import ShowPage from 'page/showPage'
import ShowPage from 'page/showPage';
import PublishPage from 'page/publish';
import DiscoverPage from 'page/discover';
import SplashScreen from 'component/splash.js';
import DeveloperPage from 'page/developer.js';
import RewardsPage from 'page/rewards.js';
import FileListDownloaded from 'page/fileListDownloaded'
import FileListPublished from 'page/fileListPublished'
import ChannelPage from 'page/channel'
import SearchPage from 'page/search'
import FileListDownloaded from 'page/fileListDownloaded';
import FileListPublished from 'page/fileListPublished';
import ChannelPage from 'page/channel';
import SearchPage from 'page/search';
const route = (page, routesMap) => {
const component = routesMap[page]
const component = routesMap[page];
return component
return component;
};
const Router = (props) => {
const {
currentPage,
params,
} = props;
const Router = props => {
const { currentPage, params } = props;
return route(currentPage, {
'settings': <SettingsPage {...params} />,
'help': <HelpPage {...params} />,
'report': <ReportPage {...params} />,
'downloaded': <FileListDownloaded {...params} />,
'published': <FileListPublished {...params} />,
'start': <StartPage {...params} />,
'wallet': <WalletPage {...params} />,
'send': <WalletPage {...params} />,
'receive': <WalletPage {...params} />,
'show': <ShowPage {...params} />,
'channel': <ChannelPage {...params} />,
'publish': <PublishPage {...params} />,
'developer': <DeveloperPage {...params} />,
'discover': <DiscoverPage {...params} />,
'rewards': <RewardsPage {...params} />,
'search': <SearchPage {...params} />,
})
}
settings: <SettingsPage {...params} />,
help: <HelpPage {...params} />,
report: <ReportPage {...params} />,
downloaded: <FileListDownloaded {...params} />,
published: <FileListPublished {...params} />,
start: <StartPage {...params} />,
wallet: <WalletPage {...params} />,
send: <WalletPage {...params} />,
receive: <WalletPage {...params} />,
show: <ShowPage {...params} />,
channel: <ChannelPage {...params} />,
publish: <PublishPage {...params} />,
developer: <DeveloperPage {...params} />,
discover: <DiscoverPage {...params} />,
rewards: <RewardsPage {...params} />,
search: <SearchPage {...params} />
});
};
export default Router
export default Router;

View file

@ -1,23 +1,16 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import {
doNavigate,
doRemoveSnackBarSnack,
} from 'actions/app'
import {
selectSnackBarSnacks,
} from 'selectors/app'
import SnackBar from './view'
import React from 'react';
import { connect } from 'react-redux';
import { doNavigate, doRemoveSnackBarSnack } from 'actions/app';
import { selectSnackBarSnacks } from 'selectors/app';
import SnackBar from './view';
const perform = (dispatch) => ({
navigate: (path) => dispatch(doNavigate(path)),
removeSnack: () => dispatch(doRemoveSnackBarSnack()),
})
const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)),
removeSnack: () => dispatch(doRemoveSnackBarSnack())
});
const select = (state) => ({
snacks: selectSnackBarSnacks(state),
})
const select = state => ({
snacks: selectSnackBarSnacks(state)
});
export default connect(select, perform)(SnackBar)
export default connect(select, perform)(SnackBar);

View file

@ -1,5 +1,5 @@
import React from 'react';
import Link from 'component/link'
import Link from 'component/link';
class SnackBar extends React.Component {
constructor(props) {
@ -10,11 +10,7 @@ class SnackBar extends React.Component {
}
render() {
const {
navigate,
snacks,
removeSnack,
} = this.props
const { navigate, snacks, removeSnack } = this.props;
if (!snacks.length) {
this._hideTimeout = null; //should be unmounting anyway, but be safe?
@ -22,25 +18,25 @@ class SnackBar extends React.Component {
}
const snack = snacks[0];
const {
message,
linkText,
linkTarget,
} = snack
const { message, linkText, linkTarget } = snack;
if (this._hideTimeout === null) {
this._hideTimeout = setTimeout(() => {
this._hideTimeout = null;
removeSnack()
removeSnack();
}, this._displayTime * 1000);
}
return (
<div className="snack-bar">
{message}
{linkText && linkTarget &&
<Link onClick={() => navigate(linkTarget)} className="snack-bar__action" label={linkText} />
}
{linkText &&
linkTarget &&
<Link
onClick={() => navigate(linkTarget)}
className="snack-bar__action"
label={linkText}
/>}
</div>
);
}

View file

@ -5,44 +5,46 @@ import LoadScreen from './load_screen.js';
export class SplashScreen extends React.Component {
static propTypes = {
message: React.PropTypes.string,
onLoadDone: React.PropTypes.func,
}
onLoadDone: React.PropTypes.func
};
constructor(props) {
super(props);
this.state = {
details: __('Starting daemon'),
message: __("Connecting"),
isLagging: false,
message: __('Connecting'),
isLagging: false
};
}
updateStatus() {
lbry.status().then((status) => { this._updateStatusCallback(status) });
lbry.status().then(status => {
this._updateStatusCallback(status);
});
}
_updateStatusCallback(status) {
const startupStatus = status.startup_status
const startupStatus = status.startup_status;
if (startupStatus.code == 'started') {
// Wait until we are able to resolve a name before declaring
// that we are done.
// TODO: This is a hack, and the logic should live in the daemon
// to give us a better sense of when we are actually started
this.setState({
message: __("Testing Network"),
details: __("Waiting for name resolution"),
message: __('Testing Network'),
details: __('Waiting for name resolution'),
isLagging: false
});
lbry.resolve({uri: "lbry://one"}).then(() => {
lbry.resolve({ uri: 'lbry://one' }).then(() => {
this.props.onLoadDone();
});
return;
}
this.setState({
details: startupStatus.message + (startupStatus.is_lagging ? '' : '...'),
isLagging: startupStatus.is_lagging,
isLagging: startupStatus.is_lagging
});
setTimeout(() => {
this.updateStatus();
@ -50,19 +52,30 @@ export class SplashScreen extends React.Component {
}
componentDidMount() {
lbry.connect()
.then(() => { this.updateStatus() })
lbry
.connect()
.then(() => {
this.updateStatus();
})
.catch(() => {
this.setState({
isLagging: true,
message: __("Connection Failure"),
details: __("Try closing all LBRY processes and starting again. If this still happpens, your anti-virus software or firewall may be preventing LBRY from connecting. Contact hello@lbry.io if you think this is a software bug.")
})
})
message: __('Connection Failure'),
details: __(
'Try closing all LBRY processes and starting again. If this still happpens, your anti-virus software or firewall may be preventing LBRY from connecting. Contact hello@lbry.io if you think this is a software bug.'
)
});
});
}
render() {
return <LoadScreen message={this.state.message} details={this.state.details} isWarning={this.state.isLagging} />
return (
<LoadScreen
message={this.state.message}
details={this.state.details}
isWarning={this.state.isLagging}
/>
);
}
}

View file

@ -1,23 +1,16 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import {
selectCurrentPage,
selectHeaderLinks,
} from 'selectors/app'
import {
doNavigate,
} from 'actions/app'
import SubHeader from './view'
import React from 'react';
import { connect } from 'react-redux';
import { selectCurrentPage, selectHeaderLinks } from 'selectors/app';
import { doNavigate } from 'actions/app';
import SubHeader from './view';
const select = (state, props) => ({
currentPage: selectCurrentPage(state),
subLinks: selectHeaderLinks(state),
})
subLinks: selectHeaderLinks(state)
});
const perform = (dispatch) => ({
navigate: (path) => dispatch(doNavigate(path)),
})
const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path))
});
export default connect(select, perform)(SubHeader)
export default connect(select, perform)(SubHeader);

View file

@ -1,29 +1,32 @@
import React from 'react'
import Link from 'component/link'
import React from 'react';
import Link from 'component/link';
const SubHeader = (props) => {
const {
subLinks,
currentPage,
navigate,
modifier,
} = props
const SubHeader = props => {
const { subLinks, currentPage, navigate, modifier } = props;
const links = []
const links = [];
for(let link of Object.keys(subLinks)) {
for (let link of Object.keys(subLinks)) {
links.push(
<Link onClick={(event) => navigate(`/${link}`, event)} key={link} className={link == currentPage ? 'sub-header-selected' : 'sub-header-unselected' }>
<Link
onClick={event => navigate(`/${link}`, event)}
key={link}
className={
link == currentPage ? 'sub-header-selected' : 'sub-header-unselected'
}
>
{subLinks[link]}
</Link>
)
);
}
return (
<nav className={'sub-header' + (modifier ? ' sub-header--' + modifier : '')}>
<nav
className={'sub-header' + (modifier ? ' sub-header--' + modifier : '')}
>
{links}
</nav>
)
}
);
};
export default SubHeader
export default SubHeader;

View file

@ -4,36 +4,47 @@ export class ToolTip extends React.Component {
static propTypes = {
body: React.PropTypes.string.isRequired,
label: React.PropTypes.string.isRequired
}
};
constructor(props) {
super(props);
this.state = {
showTooltip: false,
showTooltip: false
};
}
handleClick() {
this.setState({
showTooltip: !this.state.showTooltip,
showTooltip: !this.state.showTooltip
});
}
handleTooltipMouseOut() {
this.setState({
showTooltip: false,
showTooltip: false
});
}
render() {
return (
<span className={'tooltip ' + (this.props.className || '')}>
<a className="tooltip__link" onClick={() => { this.handleClick() }}>
<a
className="tooltip__link"
onClick={() => {
this.handleClick();
}}
>
{this.props.label}
</a>
<div className={'tooltip__body ' + (this.state.showTooltip ? '' : ' hidden')}
onMouseOut={() => { this.handleTooltipMouseOut() }}>
<div
className={
'tooltip__body ' + (this.state.showTooltip ? '' : ' hidden')
}
onMouseOut={() => {
this.handleTooltipMouseOut();
}}
>
{this.props.body}
</div>
</span>
@ -41,4 +52,4 @@ export class ToolTip extends React.Component {
}
}
export default ToolTip
export default ToolTip;

View file

@ -1,25 +1,21 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
doFetchTransactions,
} from 'actions/wallet'
import React from 'react';
import { connect } from 'react-redux';
import { doFetchTransactions } from 'actions/wallet';
import {
selectBalance,
selectTransactionItems,
selectIsFetchingTransactions,
} from 'selectors/wallet'
selectIsFetchingTransactions
} from 'selectors/wallet';
import TransactionList from './view'
import TransactionList from './view';
const select = (state) => ({
const select = state => ({
fetchingTransactions: selectIsFetchingTransactions(state),
transactionItems: selectTransactionItems(state),
})
transactionItems: selectTransactionItems(state)
});
const perform = (dispatch) => ({
const perform = dispatch => ({
fetchTransactions: () => dispatch(doFetchTransactions())
})
});
export default connect(select, perform)(TransactionList)
export default connect(select, perform)(TransactionList);

View file

@ -1,31 +1,37 @@
import React from 'react';
import {
Address,
BusyMessage,
CreditAmount
} from 'component/common';
import { Address, BusyMessage, CreditAmount } from 'component/common';
class TransactionList extends React.Component{
class TransactionList extends React.Component {
componentWillMount() {
this.props.fetchTransactions()
this.props.fetchTransactions();
}
render() {
const {
fetchingTransactions,
transactionItems,
} = this.props
const { fetchingTransactions, transactionItems } = this.props;
const rows = []
const rows = [];
if (transactionItems.length > 0) {
transactionItems.forEach(function (item) {
transactionItems.forEach(function(item) {
rows.push(
<tr key={item.id}>
<td>{ (item.amount > 0 ? '+' : '' ) + item.amount }</td>
<td>{ item.date ? item.date.toLocaleDateString() : <span className="empty">{__("(Transaction pending)")}</span> }</td>
<td>{ item.date ? item.date.toLocaleTimeString() : <span className="empty">{__("(Transaction pending)")}</span> }</td>
<td>{(item.amount > 0 ? '+' : '') + item.amount}</td>
<td>
<a className="button-text" href={"https://explorer.lbry.io/#!/transaction?id="+item.id}>{item.id.substr(0, 7)}</a>
{item.date
? item.date.toLocaleDateString()
: <span className="empty">{__('(Transaction pending)')}</span>}
</td>
<td>
{item.date
? item.date.toLocaleTimeString()
: <span className="empty">{__('(Transaction pending)')}</span>}
</td>
<td>
<a
className="button-text"
href={'https://explorer.lbry.io/#!/transaction?id=' + item.id}
>
{item.id.substr(0, 7)}
</a>
</td>
</tr>
);
@ -35,31 +41,33 @@ class TransactionList extends React.Component{
return (
<section className="card">
<div className="card__title-primary">
<h3>{__("Transaction History")}</h3>
<h3>{__('Transaction History')}</h3>
</div>
<div className="card__content">
{ fetchingTransactions && <BusyMessage message={__("Loading transactions")} /> }
{ !fetchingTransactions && rows.length === 0 ? <div className="empty">{__("You have no transactions.")}</div> : '' }
{ rows.length > 0 ?
<table className="table-standard table-stretch">
{fetchingTransactions &&
<BusyMessage message={__('Loading transactions')} />}
{!fetchingTransactions && rows.length === 0
? <div className="empty">{__('You have no transactions.')}</div>
: ''}
{rows.length > 0
? <table className="table-standard table-stretch">
<thead>
<tr>
<th>{__("Amount")}</th>
<th>{__("Date")}</th>
<th>{__("Time")}</th>
<th>{__("Transaction")}</th>
<th>{__('Amount')}</th>
<th>{__('Date')}</th>
<th>{__('Time')}</th>
<th>{__('Transaction')}</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
: ''
}
: ''}
</div>
</section>
)
);
}
}
export default TransactionList
export default TransactionList;

View file

@ -1,19 +1,13 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
doDownloadUpgrade,
doSkipUpgrade,
} from 'actions/app'
import UpgradeModal from './view'
import React from 'react';
import { connect } from 'react-redux';
import { doDownloadUpgrade, doSkipUpgrade } from 'actions/app';
import UpgradeModal from './view';
const select = (state) => ({
})
const select = state => ({});
const perform = (dispatch) => ({
const perform = dispatch => ({
downloadUpgrade: () => dispatch(doDownloadUpgrade()),
skipUpgrade: () => dispatch(doSkipUpgrade()),
})
skipUpgrade: () => dispatch(doSkipUpgrade())
});
export default connect(select, perform)(UpgradeModal)
export default connect(select, perform)(UpgradeModal);

View file

@ -1,32 +1,27 @@
import React from 'react'
import {
Modal
} from 'component/modal'
import {
downloadUpgrade,
skipUpgrade
} from 'actions/app'
import React from 'react';
import { Modal } from 'component/modal';
import { downloadUpgrade, skipUpgrade } from 'actions/app';
class UpgradeModal extends React.Component {
render() {
const {
downloadUpgrade,
skipUpgrade
} = this.props
const { downloadUpgrade, skipUpgrade } = this.props;
return (
<Modal
isOpen={true}
contentLabel={__("Update available")}
contentLabel={__('Update available')}
type="confirm"
confirmButtonLabel={__("Upgrade")}
abortButtonLabel={__("Skip")}
confirmButtonLabel={__('Upgrade')}
abortButtonLabel={__('Skip')}
onConfirmed={downloadUpgrade}
onAborted={skipUpgrade}>
{__("Your version of LBRY is out of date and may be unreliable or insecure.")}
onAborted={skipUpgrade}
>
{__(
'Your version of LBRY is out of date and may be unreliable or insecure.'
)}
</Modal>
)
);
}
}
export default UpgradeModal
export default UpgradeModal;

View file

@ -1,15 +1,9 @@
import React from 'react'
import React from 'react';
import lbryuri from 'lbryuri';
import {
connect,
} from 'react-redux'
import {
makeSelectIsResolvingForUri
} from 'selectors/content'
import {
makeSelectClaimForUri,
} from 'selectors/claims'
import UriIndicator from './view'
import { connect } from 'react-redux';
import { makeSelectIsResolvingForUri } from 'selectors/content';
import { makeSelectClaimForUri } from 'selectors/claims';
import UriIndicator from './view';
const makeSelect = () => {
const selectClaim = makeSelectClaimForUri(),
@ -18,14 +12,14 @@ const makeSelect = () => {
const select = (state, props) => ({
claim: selectClaim(state, props),
isResolvingUri: selectIsResolving(state, props),
uri: lbryuri.normalize(props.uri),
})
uri: lbryuri.normalize(props.uri)
});
return select
}
return select;
};
const perform = (dispatch) => ({
resolveUri: (uri) => dispatch(doResolveUri(uri))
})
const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri))
});
export default connect(makeSelect, perform)(UriIndicator)
export default connect(makeSelect, perform)(UriIndicator);

View file

@ -1,48 +1,39 @@
import React from 'react';
import {Icon} from 'component/common';
import { Icon } from 'component/common';
class UriIndicator extends React.Component{
class UriIndicator extends React.Component {
componentWillMount() {
this.resolve(this.props)
this.resolve(this.props);
}
componentWillReceiveProps(nextProps) {
this.resolve(nextProps)
this.resolve(nextProps);
}
resolve(props) {
const {
isResolvingUri,
resolveUri,
claim,
uri,
} = props
const { isResolvingUri, resolveUri, claim, uri } = props;
if(!isResolvingUri && claim === undefined && uri) {
resolveUri(uri)
if (!isResolvingUri && claim === undefined && uri) {
resolveUri(uri);
}
}
render() {
const {
claim,
uri,
isResolvingUri
} = this.props
const { claim, uri, isResolvingUri } = this.props;
if (isResolvingUri) {
return <span className="empty">Validating...</span>
return <span className="empty">Validating...</span>;
}
if (!claim) {
return <span className="empty">Unused</span>
return <span className="empty">Unused</span>;
}
const {
channel_name: channelName,
has_signature: hasSignature,
signature_is_valid: signatureIsValid,
} = claim
signature_is_valid: signatureIsValid
} = claim;
if (!hasSignature || !channelName) {
return <span className="empty">Anonymous</span>;
@ -59,11 +50,14 @@ class UriIndicator extends React.Component{
return (
<span>
{channelName} {' '}
{ !signatureIsValid ?
<Icon icon={icon} className={`channel-indicator__icon channel-indicator__icon--${modifier}`} /> :
'' }
{!signatureIsValid
? <Icon
icon={icon}
className={`channel-indicator__icon channel-indicator__icon--${modifier}`}
/>
: ''}
</span>
)
);
}
}

View file

@ -1,39 +1,27 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import {
doCloseModal,
} from 'actions/app'
import {
selectCurrentModal,
} from 'selectors/app'
import {
doPurchaseUri,
doLoadVideo,
} from 'actions/content'
import React from 'react';
import { connect } from 'react-redux';
import { doCloseModal } from 'actions/app';
import { selectCurrentModal } from 'selectors/app';
import { doPurchaseUri, doLoadVideo } from 'actions/content';
import {
makeSelectMetadataForUri,
makeSelectContentTypeForUri,
} from 'selectors/claims'
makeSelectContentTypeForUri
} from 'selectors/claims';
import {
makeSelectFileInfoForUri,
makeSelectLoadingForUri,
makeSelectDownloadingForUri,
} from 'selectors/file_info'
import {
makeSelectCostInfoForUri,
} from 'selectors/cost_info'
import Video from './view'
makeSelectDownloadingForUri
} from 'selectors/file_info';
import { makeSelectCostInfoForUri } from 'selectors/cost_info';
import Video from './view';
const makeSelect = () => {
const selectCostInfo = makeSelectCostInfoForUri()
const selectFileInfo = makeSelectFileInfoForUri()
const selectIsLoading = makeSelectLoadingForUri()
const selectIsDownloading = makeSelectDownloadingForUri()
const selectMetadata = makeSelectMetadataForUri()
const selectContentType = makeSelectContentTypeForUri()
const selectCostInfo = makeSelectCostInfoForUri();
const selectFileInfo = makeSelectFileInfoForUri();
const selectIsLoading = makeSelectLoadingForUri();
const selectIsDownloading = makeSelectDownloadingForUri();
const selectMetadata = makeSelectMetadataForUri();
const selectContentType = makeSelectContentTypeForUri();
const select = (state, props) => ({
costInfo: selectCostInfo(state, props),
@ -42,16 +30,16 @@ const makeSelect = () => {
modal: selectCurrentModal(state),
isLoading: selectIsLoading(state, props),
isDownloading: selectIsDownloading(state, props),
contentType: selectContentType(state, props),
})
contentType: selectContentType(state, props)
});
return select
}
return select;
};
const perform = (dispatch) => ({
loadVideo: (uri) => dispatch(doLoadVideo(uri)),
purchaseUri: (uri) => dispatch(doPurchaseUri(uri, 'affirmPurchaseAndPlay')),
closeModal: () => dispatch(doCloseModal()),
})
const perform = dispatch => ({
loadVideo: uri => dispatch(doLoadVideo(uri)),
purchaseUri: uri => dispatch(doPurchaseUri(uri, 'affirmPurchaseAndPlay')),
closeModal: () => dispatch(doCloseModal())
});
export default connect(makeSelect, perform)(Video)
export default connect(makeSelect, perform)(Video);

View file

@ -1,25 +1,23 @@
import React from 'react';
import FilePrice from 'component/filePrice'
import FilePrice from 'component/filePrice';
import Link from 'component/link';
import Modal from 'component/modal';
import lbry from 'lbry'
import {
Thumbnail,
} from 'component/common'
import lbry from 'lbry';
import { Thumbnail } from 'component/common';
class VideoPlayButton extends React.Component {
onPurchaseConfirmed() {
this.props.closeModal()
this.props.startPlaying()
this.props.loadVideo(this.props.uri)
this.props.closeModal();
this.props.startPlaying();
this.props.loadVideo(this.props.uri);
}
onWatchClick() {
this.props.purchaseUri(this.props.uri).then(() => {
if (!this.props.modal) {
this.props.startPlaying()
this.props.startPlaying();
}
})
});
}
render() {
@ -28,17 +26,15 @@ class VideoPlayButton extends React.Component {
label,
className,
metadata,
metadata: {
title,
},
metadata: { title },
uri,
modal,
closeModal,
isLoading,
costInfo,
fileInfo,
mediaType,
} = this.props
mediaType
} = this.props;
/*
title={
@ -48,45 +44,64 @@ class VideoPlayButton extends React.Component {
}
*/
const disabled = isLoading || fileInfo === undefined || (fileInfo === null && (!costInfo || costInfo.cost === undefined))
const icon = ["audio", "video"].indexOf(mediaType) !== -1 ? "icon-play" : "icon-folder-o"
const disabled =
isLoading ||
fileInfo === undefined ||
(fileInfo === null && (!costInfo || costInfo.cost === undefined));
const icon = ['audio', 'video'].indexOf(mediaType) !== -1
? 'icon-play'
: 'icon-folder-o';
return (<div>
<Link button={ button ? button : null }
return (
<div>
<Link
button={button ? button : null}
disabled={disabled}
label={label ? label : ""}
label={label ? label : ''}
className="video__play-button"
icon={icon}
onClick={this.onWatchClick.bind(this)} />
<Modal contentLabel={__("Not enough credits")} isOpen={modal == 'notEnoughCredits'} onConfirmed={closeModal}>
onClick={this.onWatchClick.bind(this)}
/>
<Modal
contentLabel={__('Not enough credits')}
isOpen={modal == 'notEnoughCredits'}
onConfirmed={closeModal}
>
{__("You don't have enough LBRY credits to pay for this stream.")}
</Modal>
<Modal
type="confirm"
isOpen={modal == 'affirmPurchaseAndPlay'}
contentLabel={__("Confirm Purchase")}
contentLabel={__('Confirm Purchase')}
onConfirmed={this.onPurchaseConfirmed.bind(this)}
onAborted={closeModal}>
{__("This will purchase")} <strong>{title}</strong> {__("for")} <strong><FilePrice uri={uri} look="plain" /></strong> {__("credits")}.
onAborted={closeModal}
>
{__('This will purchase')} <strong>{title}</strong> {__('for')}
{' '}<strong><FilePrice uri={uri} look="plain" /></strong>
{' '}{__('credits')}.
</Modal>
<Modal
isOpen={modal == 'timedOut'} onConfirmed={closeModal} contentLabel={__("Timed Out")}>
{__("Sorry, your download timed out :(")}
isOpen={modal == 'timedOut'}
onConfirmed={closeModal}
contentLabel={__('Timed Out')}
>
{__('Sorry, your download timed out :(')}
</Modal>
</div>);
</div>
);
}
}
class Video extends React.Component {
constructor(props) {
super(props)
this.state = { isPlaying: false }
super(props);
this.state = { isPlaying: false };
}
startPlaying() {
this.setState({
isPlaying: true
})
});
}
render() {
@ -95,88 +110,105 @@ class Video extends React.Component {
isLoading,
isDownloading,
fileInfo,
contentType
} = this.props;
const { isPlaying = false } = this.state;
const isReadyToPlay = fileInfo && fileInfo.written_bytes > 0;
const mediaType = lbry.getMediaType(
contentType,
} = this.props
const {
isPlaying = false,
} = this.state
fileInfo && fileInfo.file_name
);
const isReadyToPlay = fileInfo && fileInfo.written_bytes > 0
const mediaType = lbry.getMediaType(contentType, fileInfo && fileInfo.file_name)
let loadStatusMessage = '';
let loadStatusMessage = ''
if(fileInfo && fileInfo.completed && !fileInfo.written_bytes) {
loadStatusMessage = __("It looks like you deleted or moved this file. We're rebuilding it now. It will only take a few seconds.")
if (fileInfo && fileInfo.completed && !fileInfo.written_bytes) {
loadStatusMessage = __(
"It looks like you deleted or moved this file. We're rebuilding it now. It will only take a few seconds."
);
} else if (isLoading) {
loadStatusMessage = __("Requesting stream... it may sit here for like 15-20 seconds in a really awkward way... we're working on it")
loadStatusMessage = __(
"Requesting stream... it may sit here for like 15-20 seconds in a really awkward way... we're working on it"
);
} else if (isDownloading) {
loadStatusMessage = __("Downloading stream... not long left now!")
loadStatusMessage = __('Downloading stream... not long left now!');
}
let klassName = ""
if (isLoading || isDownloading) klassName += "video-embedded video"
if (mediaType === "video") {
klassName += "video-embedded video"
klassName += isPlaying ? " video--active" : " video--hidden"
} else if (mediaType === "application") {
klassName += "video-embedded"
let klassName = '';
if (isLoading || isDownloading) klassName += 'video-embedded video';
if (mediaType === 'video') {
klassName += 'video-embedded video';
klassName += isPlaying ? ' video--active' : ' video--hidden';
} else if (mediaType === 'application') {
klassName += 'video-embedded';
} else {
if (!isPlaying) klassName += "video-embedded"
if (!isPlaying) klassName += 'video-embedded';
}
const poster = metadata.thumbnail
const poster = metadata.thumbnail;
return (
<div className={klassName}>{
isPlaying ?
(!isReadyToPlay ?
<span>{__("this is the world's worst loading screen and we shipped our software with it anyway...")} <br /><br />{loadStatusMessage}</span> :
<VideoPlayer filename={fileInfo.file_name} poster={poster} downloadPath={fileInfo.download_path} mediaType={mediaType} poster={poster} />) :
<div className="video__cover" style={{backgroundImage: 'url("' + metadata.thumbnail + '")'}}>
<VideoPlayButton startPlaying={this.startPlaying.bind(this)} {...this.props} mediaType={mediaType} />
<div className={klassName}>
{isPlaying
? !isReadyToPlay
? <span>
{__(
"this is the world's worst loading screen and we shipped our software with it anyway..."
)}
{' '}<br /><br />{loadStatusMessage}
</span>
: <VideoPlayer
filename={fileInfo.file_name}
poster={poster}
downloadPath={fileInfo.download_path}
mediaType={mediaType}
poster={poster}
/>
: <div
className="video__cover"
style={{ backgroundImage: 'url("' + metadata.thumbnail + '")' }}
>
<VideoPlayButton
startPlaying={this.startPlaying.bind(this)}
{...this.props}
mediaType={mediaType}
/>
</div>}
</div>
}</div>
);
}
}
const from = require('from2')
const player = require('render-media')
const fs = require('fs')
const from = require('from2');
const player = require('render-media');
const fs = require('fs');
class VideoPlayer extends React.Component {
componentDidMount() {
const elem = this.refs.media
const {
downloadPath,
filename,
} = this.props
const elem = this.refs.media;
const { downloadPath, filename } = this.props;
const file = {
name: filename,
createReadStream: (opts) => {
return fs.createReadStream(downloadPath, opts)
}
createReadStream: opts => {
return fs.createReadStream(downloadPath, opts);
}
};
player.append(file, elem, {
autoplay: true,
controls: true,
})
controls: true
});
}
render() {
const {
downloadPath,
mediaType,
poster,
} = this.props
const { downloadPath, mediaType, poster } = this.props;
return (
<div>
{["audio", "application"].indexOf(mediaType) !== -1 && <Thumbnail src={poster} className="video-embedded" />}
{['audio', 'application'].indexOf(mediaType) !== -1 &&
<Thumbnail src={poster} className="video-embedded" />}
<div ref="media" />
</div>
)
);
}
}
export default Video
export default Video;

View file

@ -1,25 +1,20 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
doCheckAddressIsMine,
doGetNewAddress,
} from 'actions/wallet'
import React from 'react';
import { connect } from 'react-redux';
import { doCheckAddressIsMine, doGetNewAddress } from 'actions/wallet';
import {
selectReceiveAddress,
selectGettingNewAddress
} from 'selectors/wallet'
import WalletPage from './view'
} from 'selectors/wallet';
import WalletPage from './view';
const select = (state) => ({
const select = state => ({
receiveAddress: selectReceiveAddress(state),
gettingNewAddress: selectGettingNewAddress(state),
})
gettingNewAddress: selectGettingNewAddress(state)
});
const perform = (dispatch) => ({
checkAddressIsMine: (address) => dispatch(doCheckAddressIsMine(address)),
getNewAddress: () => dispatch(doGetNewAddress()),
})
const perform = dispatch => ({
checkAddressIsMine: address => dispatch(doCheckAddressIsMine(address)),
getNewAddress: () => dispatch(doGetNewAddress())
});
export default connect(select, perform)(WalletPage)
export default connect(select, perform)(WalletPage);

View file

@ -1,36 +1,44 @@
import React from 'react';
import Link from 'component/link';
import {
Address
} from 'component/common';
import { Address } from 'component/common';
class WalletAddress extends React.Component {
componentWillMount() {
this.props.checkAddressIsMine(this.props.receiveAddress)
this.props.checkAddressIsMine(this.props.receiveAddress);
}
render() {
const {
receiveAddress,
getNewAddress,
gettingNewAddress,
} = this.props
const { receiveAddress, getNewAddress, gettingNewAddress } = this.props;
return (
<section className="card">
<div className="card__title-primary">
<h3>{__("Wallet Address")}</h3>
<h3>{__('Wallet Address')}</h3>
</div>
<div className="card__content">
<Address address={receiveAddress} />
</div>
<div className="card__actions">
<Link label={__("Get New Address")} button="primary" icon='icon-refresh' onClick={getNewAddress} disabled={gettingNewAddress} />
<Link
label={__('Get New Address')}
button="primary"
icon="icon-refresh"
onClick={getNewAddress}
disabled={gettingNewAddress}
/>
</div>
<div className="card__content">
<div className="help">
<p>{__("Other LBRY users may send credits to you by entering this address on the \"Send\" page.")}</p>
<p>{__("You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.")}</p>
<p>
{__(
'Other LBRY users may send credits to you by entering this address on the "Send" page.'
)}
</p>
<p>
{__(
'You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.'
)}
</p>
</div>
</div>
</section>
@ -38,4 +46,4 @@ class WalletAddress extends React.Component {
}
}
export default WalletAddress
export default WalletAddress;

View file

@ -1,36 +1,31 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
doCloseModal,
} from 'actions/app'
import React from 'react';
import { connect } from 'react-redux';
import { doCloseModal } from 'actions/app';
import {
doSendDraftTransaction,
doSetDraftTransactionAmount,
doSetDraftTransactionAddress,
} from 'actions/wallet'
import {
selectCurrentModal,
} from 'selectors/app'
doSetDraftTransactionAddress
} from 'actions/wallet';
import { selectCurrentModal } from 'selectors/app';
import {
selectDraftTransactionAmount,
selectDraftTransactionAddress,
} from 'selectors/wallet'
selectDraftTransactionAddress
} from 'selectors/wallet';
import WalletSend from './view'
import WalletSend from './view';
const select = (state) => ({
const select = state => ({
modal: selectCurrentModal(state),
address: selectDraftTransactionAddress(state),
amount: selectDraftTransactionAmount(state),
})
amount: selectDraftTransactionAmount(state)
});
const perform = (dispatch) => ({
const perform = dispatch => ({
closeModal: () => dispatch(doCloseModal()),
sendToAddress: () => dispatch(doSendDraftTransaction()),
setAmount: (event) => dispatch(doSetDraftTransactionAmount(event.target.value)),
setAddress: (event) => dispatch(doSetDraftTransactionAddress(event.target.value)),
})
setAmount: event => dispatch(doSetDraftTransactionAmount(event.target.value)),
setAddress: event =>
dispatch(doSetDraftTransactionAddress(event.target.value))
});
export default connect(select, perform)(WalletSend)
export default connect(select, perform)(WalletSend);

View file

@ -1,11 +1,9 @@
import React from 'react';
import Link from 'component/link';
import Modal from 'component/modal';
import {
FormRow
} from 'component/form';
import { FormRow } from 'component/form';
const WalletSend = (props) => {
const WalletSend = props => {
const {
sendToAddress,
closeModal,
@ -13,37 +11,75 @@ const WalletSend = (props) => {
setAmount,
setAddress,
amount,
address,
} = props
address
} = props;
return (
<section className="card">
<form onSubmit={sendToAddress}>
<div className="card__title-primary">
<h3>{__("Send Credits")}</h3>
<h3>{__('Send Credits')}</h3>
</div>
<div className="card__content">
<FormRow label={__("Amount")} postfix="LBC" step="0.01" type="number" placeholder="1.23" size="10" onChange={setAmount} value={amount} />
<FormRow
label={__('Amount')}
postfix="LBC"
step="0.01"
type="number"
placeholder="1.23"
size="10"
onChange={setAmount}
value={amount}
/>
</div>
<div className="card__content">
<FormRow label={__("Recipient Address")} placeholder="bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs" type="text" size="60" onChange={setAddress} value={address} />
<FormRow
label={__('Recipient Address')}
placeholder="bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs"
type="text"
size="60"
onChange={setAddress}
value={address}
/>
</div>
<div className="card__actions card__actions--form-submit">
<Link button="primary" label={__("Send")} onClick={sendToAddress} disabled={!(parseFloat(amount) > 0.0) || !address} />
<input type='submit' className='hidden' />
<Link
button="primary"
label={__('Send')}
onClick={sendToAddress}
disabled={!(parseFloat(amount) > 0.0) || !address}
/>
<input type="submit" className="hidden" />
</div>
</form>
{modal == 'insufficientBalance' && <Modal isOpen={true} contentLabel={__("Insufficient balance")} onConfirmed={closeModal}>
{__("Insufficient balance: after this transaction you would have less than 1 LBC in your wallet.")}
{modal == 'insufficientBalance' &&
<Modal
isOpen={true}
contentLabel={__('Insufficient balance')}
onConfirmed={closeModal}
>
{__(
'Insufficient balance: after this transaction you would have less than 1 LBC in your wallet.'
)}
</Modal>}
{modal == 'transactionSuccessful' && <Modal isOpen={true} contentLabel={__("Transaction successful")} onConfirmed={closeModal}>
{__("Your transaction was successfully placed in the queue.")}
{modal == 'transactionSuccessful' &&
<Modal
isOpen={true}
contentLabel={__('Transaction successful')}
onConfirmed={closeModal}
>
{__('Your transaction was successfully placed in the queue.')}
</Modal>}
{modal == 'transactionFailed' && <Modal isOpen={true} contentLabel={__("Transaction failed")} onConfirmed={closeModal}>
{__("Something went wrong")}:
{modal == 'transactionFailed' &&
<Modal
isOpen={true}
contentLabel={__('Transaction failed')}
onConfirmed={closeModal}
>
{__('Something went wrong')}:
</Modal>}
</section>
)
}
);
};
export default WalletSend
export default WalletSend;

View file

@ -1,25 +1,19 @@
import React from 'react'
import {
connect
} from 'react-redux'
import lbryuri from 'lbryuri.js'
import {
selectWunderBarAddress,
selectWunderBarIcon
} from 'selectors/search'
import {
doNavigate,
} from 'actions/app'
import Wunderbar from './view'
import React from 'react';
import { connect } from 'react-redux';
import lbryuri from 'lbryuri.js';
import { selectWunderBarAddress, selectWunderBarIcon } from 'selectors/search';
import { doNavigate } from 'actions/app';
import Wunderbar from './view';
const select = (state) => ({
const select = state => ({
address: selectWunderBarAddress(state),
icon: selectWunderBarIcon(state)
})
});
const perform = (dispatch) => ({
onSearch: (query) => dispatch(doNavigate('/search', { query, })),
onSubmit: (query) => dispatch(doNavigate('/show', { uri: lbryuri.normalize(query) } ))
})
const perform = dispatch => ({
onSearch: query => dispatch(doNavigate('/search', { query })),
onSubmit: query =>
dispatch(doNavigate('/show', { uri: lbryuri.normalize(query) }))
});
export default connect(select, perform)(Wunderbar)
export default connect(select, perform)(Wunderbar);

View file

@ -1,14 +1,14 @@
import React from 'react';
import lbryuri from 'lbryuri.js';
import {Icon} from 'component/common.js';
import { Icon } from 'component/common.js';
class WunderBar extends React.PureComponent {
static TYPING_TIMEOUT = 800
static TYPING_TIMEOUT = 800;
static propTypes = {
onSearch: React.PropTypes.func.isRequired,
onSubmit: React.PropTypes.func.isRequired
}
};
constructor(props) {
super(props);
@ -35,13 +35,11 @@ class WunderBar extends React.PureComponent {
}
onChange(event) {
if (this._userTypingTimer)
{
if (this._userTypingTimer) {
clearTimeout(this._userTypingTimer);
}
this.setState({ address: event.target.value })
this.setState({ address: event.target.value });
this._isSearchDispatchPending = true;
@ -58,7 +56,10 @@ class WunderBar extends React.PureComponent {
}
componentWillReceiveProps(nextProps) {
if (nextProps.viewingPage !== this.props.viewingPage || nextProps.address != this.props.address) {
if (
nextProps.viewingPage !== this.props.viewingPage ||
nextProps.address != this.props.address
) {
this.setState({ address: nextProps.address, icon: nextProps.icon });
}
}
@ -66,14 +67,17 @@ class WunderBar extends React.PureComponent {
onFocus() {
this._stateBeforeSearch = this.state;
let newState = {
icon: "icon-search",
icon: 'icon-search',
isActive: true
}
};
this._focusPending = true;
//below is hacking, improved when we have proper routing
if (!this.state.address.startsWith('lbry://') && this.state.icon !== "icon-search") //onFocus, if they are not on an exact URL or a search page, clear the bar
{
if (
!this.state.address.startsWith('lbry://') &&
this.state.icon !== 'icon-search'
) {
//onFocus, if they are not on an exact URL or a search page, clear the bar
newState.address = '';
}
this.setState(newState);
@ -83,14 +87,13 @@ class WunderBar extends React.PureComponent {
if (this._isSearchDispatchPending) {
setTimeout(() => {
this.onBlur();
}, WunderBar.TYPING_TIMEOUT + 1)
}, WunderBar.TYPING_TIMEOUT + 1);
} else {
let commonState = {isActive: false};
let commonState = { isActive: false };
if (this._resetOnNextBlur) {
this.setState(Object.assign({}, this._stateBeforeSearch, commonState));
this._input.value = this.state.address;
}
else {
} else {
this._resetOnNextBlur = true;
this._stateBeforeSearch = this.state;
this.setState(commonState);
@ -116,9 +119,8 @@ class WunderBar extends React.PureComponent {
onKeyPress(event) {
if (event.charCode == 13 && this._input.value) {
let uri = null,
method = "onSubmit";
method = 'onSubmit';
this._resetOnNextBlur = false;
clearTimeout(this._userTypingTimer);
@ -126,9 +128,10 @@ class WunderBar extends React.PureComponent {
try {
uri = lbryuri.normalize(this._input.value);
this.setState({ value: uri });
} catch (error) { //then it's not a valid URL, so let's search
} catch (error) {
//then it's not a valid URL, so let's search
uri = this._input.value;
method = "onSearch";
method = 'onSearch';
}
this.props[method](uri);
@ -142,16 +145,23 @@ class WunderBar extends React.PureComponent {
render() {
return (
<div className={'wunderbar' + (this.state.isActive ? ' wunderbar--active' : '')}>
{this.state.icon ? <Icon fixed icon={this.state.icon} /> : '' }
<input className="wunderbar__input" type="search"
<div
className={
'wunderbar' + (this.state.isActive ? ' wunderbar--active' : '')
}
>
{this.state.icon ? <Icon fixed icon={this.state.icon} /> : ''}
<input
className="wunderbar__input"
type="search"
ref={this.onReceiveRef}
onFocus={this.onFocus}
onBlur={this.onBlur}
onChange={this.onChange}
onKeyPress={this.onKeyPress}
value={this.state.address}
placeholder={__("Find movies, music, games, and more")} />
placeholder={__('Find movies, music, games, and more')}
/>
</div>
);
}

View file

@ -1,2 +1 @@
module.exports = {
}
module.exports = {};

View file

@ -1,2 +1 @@
module.exports = {
}
module.exports = {};

View file

@ -1,68 +1,71 @@
export const CHANGE_PATH = 'CHANGE_PATH'
export const OPEN_MODAL = 'OPEN_MODAL'
export const CLOSE_MODAL = 'CLOSE_MODAL'
export const HISTORY_BACK = 'HISTORY_BACK'
export const SHOW_SNACKBAR = 'SHOW_SNACKBAR'
export const REMOVE_SNACKBAR_SNACK = 'REMOVE_SNACKBAR_SNACK'
export const CHANGE_PATH = 'CHANGE_PATH';
export const OPEN_MODAL = 'OPEN_MODAL';
export const CLOSE_MODAL = 'CLOSE_MODAL';
export const HISTORY_BACK = 'HISTORY_BACK';
export const SHOW_SNACKBAR = 'SHOW_SNACKBAR';
export const REMOVE_SNACKBAR_SNACK = 'REMOVE_SNACKBAR_SNACK';
export const DAEMON_READY = 'DAEMON_READY'
export const DAEMON_READY = 'DAEMON_READY';
// Upgrades
export const UPGRADE_CANCELLED = 'UPGRADE_CANCELLED'
export const DOWNLOAD_UPGRADE = 'DOWNLOAD_UPGRADE'
export const UPGRADE_DOWNLOAD_STARTED = 'UPGRADE_DOWNLOAD_STARTED'
export const UPGRADE_DOWNLOAD_COMPLETED = 'UPGRADE_DOWNLOAD_COMPLETED'
export const UPGRADE_DOWNLOAD_PROGRESSED = 'UPGRADE_DOWNLOAD_PROGRESSED'
export const CHECK_UPGRADE_AVAILABLE = 'CHECK_UPGRADE_AVAILABLE'
export const UPDATE_VERSION = 'UPDATE_VERSION'
export const SKIP_UPGRADE = 'SKIP_UPGRADE'
export const START_UPGRADE = 'START_UPGRADE'
export const UPGRADE_CANCELLED = 'UPGRADE_CANCELLED';
export const DOWNLOAD_UPGRADE = 'DOWNLOAD_UPGRADE';
export const UPGRADE_DOWNLOAD_STARTED = 'UPGRADE_DOWNLOAD_STARTED';
export const UPGRADE_DOWNLOAD_COMPLETED = 'UPGRADE_DOWNLOAD_COMPLETED';
export const UPGRADE_DOWNLOAD_PROGRESSED = 'UPGRADE_DOWNLOAD_PROGRESSED';
export const CHECK_UPGRADE_AVAILABLE = 'CHECK_UPGRADE_AVAILABLE';
export const UPDATE_VERSION = 'UPDATE_VERSION';
export const SKIP_UPGRADE = 'SKIP_UPGRADE';
export const START_UPGRADE = 'START_UPGRADE';
// Wallet
export const GET_NEW_ADDRESS_STARTED = 'GET_NEW_ADDRESS_STARTED'
export const GET_NEW_ADDRESS_COMPLETED = 'GET_NEW_ADDRESS_COMPLETED'
export const FETCH_TRANSACTIONS_STARTED = 'FETCH_TRANSACTIONS_STARTED'
export const FETCH_TRANSACTIONS_COMPLETED = 'FETCH_TRANSACTIONS_COMPLETED'
export const UPDATE_BALANCE = 'UPDATE_BALANCE'
export const CHECK_ADDRESS_IS_MINE_STARTED = 'CHECK_ADDRESS_IS_MINE_STARTED'
export const CHECK_ADDRESS_IS_MINE_COMPLETED = 'CHECK_ADDRESS_IS_MINE_COMPLETED'
export const SET_DRAFT_TRANSACTION_AMOUNT = 'SET_DRAFT_TRANSACTION_AMOUNT'
export const SET_DRAFT_TRANSACTION_ADDRESS = 'SET_DRAFT_TRANSACTION_ADDRESS'
export const SEND_TRANSACTION_STARTED = 'SEND_TRANSACTION_STARTED'
export const SEND_TRANSACTION_COMPLETED = 'SEND_TRANSACTION_COMPLETED'
export const SEND_TRANSACTION_FAILED = 'SEND_TRANSACTION_FAILED'
export const GET_NEW_ADDRESS_STARTED = 'GET_NEW_ADDRESS_STARTED';
export const GET_NEW_ADDRESS_COMPLETED = 'GET_NEW_ADDRESS_COMPLETED';
export const FETCH_TRANSACTIONS_STARTED = 'FETCH_TRANSACTIONS_STARTED';
export const FETCH_TRANSACTIONS_COMPLETED = 'FETCH_TRANSACTIONS_COMPLETED';
export const UPDATE_BALANCE = 'UPDATE_BALANCE';
export const CHECK_ADDRESS_IS_MINE_STARTED = 'CHECK_ADDRESS_IS_MINE_STARTED';
export const CHECK_ADDRESS_IS_MINE_COMPLETED =
'CHECK_ADDRESS_IS_MINE_COMPLETED';
export const SET_DRAFT_TRANSACTION_AMOUNT = 'SET_DRAFT_TRANSACTION_AMOUNT';
export const SET_DRAFT_TRANSACTION_ADDRESS = 'SET_DRAFT_TRANSACTION_ADDRESS';
export const SEND_TRANSACTION_STARTED = 'SEND_TRANSACTION_STARTED';
export const SEND_TRANSACTION_COMPLETED = 'SEND_TRANSACTION_COMPLETED';
export const SEND_TRANSACTION_FAILED = 'SEND_TRANSACTION_FAILED';
// Content
export const FETCH_FEATURED_CONTENT_STARTED = 'FETCH_FEATURED_CONTENT_STARTED'
export const FETCH_FEATURED_CONTENT_COMPLETED = 'FETCH_FEATURED_CONTENT_COMPLETED'
export const RESOLVE_URI_STARTED = 'RESOLVE_URI_STARTED'
export const RESOLVE_URI_COMPLETED = 'RESOLVE_URI_COMPLETED'
export const RESOLVE_URI_CANCELED = 'RESOLVE_URI_CANCELED'
export const FETCH_CHANNEL_CLAIMS_STARTED = 'FETCH_CHANNEL_CLAIMS_STARTED'
export const FETCH_CHANNEL_CLAIMS_COMPLETED = 'FETCH_CHANNEL_CLAIMS_COMPLETED'
export const FETCH_CLAIM_LIST_MINE_STARTED = 'FETCH_CLAIM_LIST_MINE_STARTED'
export const FETCH_CLAIM_LIST_MINE_COMPLETED = 'FETCH_CLAIM_LIST_MINE_COMPLETED'
export const FILE_LIST_STARTED = 'FILE_LIST_STARTED'
export const FILE_LIST_COMPLETED = 'FILE_LIST_COMPLETED'
export const FETCH_FILE_INFO_STARTED = 'FETCH_FILE_INFO_STARTED'
export const FETCH_FILE_INFO_COMPLETED = 'FETCH_FILE_INFO_COMPLETED'
export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED'
export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED'
export const LOADING_VIDEO_STARTED = 'LOADING_VIDEO_STARTED'
export const LOADING_VIDEO_COMPLETED = 'LOADING_VIDEO_COMPLETED'
export const LOADING_VIDEO_FAILED = 'LOADING_VIDEO_FAILED'
export const DOWNLOADING_STARTED = 'DOWNLOADING_STARTED'
export const DOWNLOADING_PROGRESSED = 'DOWNLOADING_PROGRESSED'
export const DOWNLOADING_COMPLETED = 'DOWNLOADING_COMPLETED'
export const PLAY_VIDEO_STARTED = 'PLAY_VIDEO_STARTED'
export const FETCH_AVAILABILITY_STARTED = 'FETCH_AVAILABILITY_STARTED'
export const FETCH_AVAILABILITY_COMPLETED = 'FETCH_AVAILABILITY_COMPLETED'
export const FILE_DELETE = 'FILE_DELETE'
export const FETCH_FEATURED_CONTENT_STARTED = 'FETCH_FEATURED_CONTENT_STARTED';
export const FETCH_FEATURED_CONTENT_COMPLETED =
'FETCH_FEATURED_CONTENT_COMPLETED';
export const RESOLVE_URI_STARTED = 'RESOLVE_URI_STARTED';
export const RESOLVE_URI_COMPLETED = 'RESOLVE_URI_COMPLETED';
export const RESOLVE_URI_CANCELED = 'RESOLVE_URI_CANCELED';
export const FETCH_CHANNEL_CLAIMS_STARTED = 'FETCH_CHANNEL_CLAIMS_STARTED';
export const FETCH_CHANNEL_CLAIMS_COMPLETED = 'FETCH_CHANNEL_CLAIMS_COMPLETED';
export const FETCH_CLAIM_LIST_MINE_STARTED = 'FETCH_CLAIM_LIST_MINE_STARTED';
export const FETCH_CLAIM_LIST_MINE_COMPLETED =
'FETCH_CLAIM_LIST_MINE_COMPLETED';
export const FILE_LIST_STARTED = 'FILE_LIST_STARTED';
export const FILE_LIST_COMPLETED = 'FILE_LIST_COMPLETED';
export const FETCH_FILE_INFO_STARTED = 'FETCH_FILE_INFO_STARTED';
export const FETCH_FILE_INFO_COMPLETED = 'FETCH_FILE_INFO_COMPLETED';
export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED';
export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED';
export const LOADING_VIDEO_STARTED = 'LOADING_VIDEO_STARTED';
export const LOADING_VIDEO_COMPLETED = 'LOADING_VIDEO_COMPLETED';
export const LOADING_VIDEO_FAILED = 'LOADING_VIDEO_FAILED';
export const DOWNLOADING_STARTED = 'DOWNLOADING_STARTED';
export const DOWNLOADING_PROGRESSED = 'DOWNLOADING_PROGRESSED';
export const DOWNLOADING_COMPLETED = 'DOWNLOADING_COMPLETED';
export const PLAY_VIDEO_STARTED = 'PLAY_VIDEO_STARTED';
export const FETCH_AVAILABILITY_STARTED = 'FETCH_AVAILABILITY_STARTED';
export const FETCH_AVAILABILITY_COMPLETED = 'FETCH_AVAILABILITY_COMPLETED';
export const FILE_DELETE = 'FILE_DELETE';
// Search
export const SEARCH_STARTED = 'SEARCH_STARTED'
export const SEARCH_COMPLETED = 'SEARCH_COMPLETED'
export const SEARCH_CANCELLED = 'SEARCH_CANCELLED'
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'
export const DAEMON_SETTINGS_RECEIVED = 'DAEMON_SETTINGS_RECEIVED';

View file

@ -1,18 +1,28 @@
const jsonrpc = {};
jsonrpc.call = function (connectionString, method, params, callback, errorCallback, connectFailedCallback, timeout) {
var xhr = new XMLHttpRequest;
jsonrpc.call = function(
connectionString,
method,
params,
callback,
errorCallback,
connectFailedCallback,
timeout
) {
var xhr = new XMLHttpRequest();
if (typeof connectFailedCallback !== 'undefined') {
if (timeout) {
xhr.timeout = timeout;
}
xhr.addEventListener('error', function (e) {
xhr.addEventListener('error', function(e) {
connectFailedCallback(e);
});
xhr.addEventListener('timeout', function() {
connectFailedCallback(new Error(__('XMLHttpRequest connection timed out')));
})
connectFailedCallback(
new Error(__('XMLHttpRequest connection timed out'))
);
});
}
xhr.addEventListener('load', function() {
var response = JSON.parse(xhr.responseText);
@ -31,7 +41,7 @@ jsonrpc.call = function (connectionString, method, params, callback, errorCallba
data: response.error.data
}
});
document.dispatchEvent(errorEvent)
document.dispatchEvent(errorEvent);
}
} else if (callback) {
callback(response.result);
@ -39,11 +49,11 @@ jsonrpc.call = function (connectionString, method, params, callback, errorCallba
});
if (connectFailedCallback) {
xhr.addEventListener('error', function (event) {
xhr.addEventListener('error', function(event) {
connectFailedCallback(event);
});
} else {
xhr.addEventListener('error', function (event) {
xhr.addEventListener('error', function(event) {
var errorEvent = new CustomEvent('unhandledError', {
detail: {
connectionString: connectionString,
@ -60,16 +70,18 @@ jsonrpc.call = function (connectionString, method, params, callback, errorCallba
const counter = parseInt(sessionStorage.getItem('JSONRPCCounter') || 0);
xhr.open('POST', connectionString, true);
xhr.send(JSON.stringify({
'jsonrpc': '2.0',
'method': method,
'params': params,
'id': counter,
}));
xhr.send(
JSON.stringify({
jsonrpc: '2.0',
method: method,
params: params,
id: counter
})
);
sessionStorage.setItem('JSONRPCCounter', counter + 1);
return xhr
return xhr;
};
export default jsonrpc;

View file

@ -2,9 +2,9 @@ import lbryio from './lbryio.js';
import lighthouse from './lighthouse.js';
import jsonrpc from './jsonrpc.js';
import lbryuri from './lbryuri.js';
import {getLocal, getSession, setSession, setLocal} from './utils.js';
import { getLocal, getSession, setSession, setLocal } from './utils.js';
const {remote, ipcRenderer} = require('electron');
const { remote, ipcRenderer } = require('electron');
const menu = remote.require('./menu/main-menu');
let lbry = {
@ -18,7 +18,7 @@ let lbry = {
useCustomLighthouseServers: false,
customLighthouseServers: [],
showDeveloperMenu: false,
language: 'en',
language: 'en'
}
};
@ -26,21 +26,22 @@ let lbry = {
* Records a publish attempt in local storage. Returns a dictionary with all the data needed to
* needed to make a dummy claim or file info object.
*/
function savePendingPublish({name, channel_name}) {
function savePendingPublish({ name, channel_name }) {
let uri;
if (channel_name) {
uri = lbryuri.build({name: channel_name, path: name}, false);
uri = lbryuri.build({ name: channel_name, path: name }, false);
} else {
uri = lbryuri.build({name: name}, false);
uri = lbryuri.build({ name: name }, false);
}
const pendingPublishes = getLocal('pendingPublishes') || [];
const newPendingPublish = {
name, channel_name,
name,
channel_name,
claim_id: 'pending_claim_' + uri,
txid: 'pending_' + uri,
nout: 0,
outpoint: 'pending_' + uri + ':0',
time: Date.now(),
time: Date.now()
};
setLocal('pendingPublishes', [...pendingPublishes, newPendingPublish]);
return newPendingPublish;
@ -50,12 +51,19 @@ function savePendingPublish({name, channel_name}) {
* If there is a pending publish with the given name or outpoint, remove it.
* A channel name may also be provided along with name.
*/
function removePendingPublishIfNeeded({name, channel_name, outpoint}) {
function removePendingPublishIfNeeded({ name, channel_name, outpoint }) {
function pubMatches(pub) {
return pub.outpoint === outpoint || (pub.name === name && (!channel_name || pub.channel_name === channel_name));
return (
pub.outpoint === outpoint ||
(pub.name === name &&
(!channel_name || pub.channel_name === channel_name))
);
}
setLocal('pendingPublishes', lbry.getPendingPublishes().filter(pub => !pubMatches(pub)));
setLocal(
'pendingPublishes',
lbry.getPendingPublishes().filter(pub => !pubMatches(pub))
);
}
/**
@ -64,57 +72,89 @@ function removePendingPublishIfNeeded({name, channel_name, outpoint}) {
*/
lbry.getPendingPublishes = function() {
const pendingPublishes = getLocal('pendingPublishes') || [];
const newPendingPublishes = pendingPublishes.filter(pub => Date.now() - pub.time <= lbry.pendingPublishTimeout);
const newPendingPublishes = pendingPublishes.filter(
pub => Date.now() - pub.time <= lbry.pendingPublishTimeout
);
setLocal('pendingPublishes', newPendingPublishes);
return newPendingPublishes;
}
};
/**
* Gets a pending publish attempt by its name or (fake) outpoint. A channel name can also be
* provided along withe the name. If no pending publish is found, returns null.
*/
function getPendingPublish({name, channel_name, outpoint}) {
function getPendingPublish({ name, channel_name, outpoint }) {
const pendingPublishes = lbry.getPendingPublishes();
return pendingPublishes.find(
pub => pub.outpoint === outpoint || (pub.name === name && (!channel_name || pub.channel_name === channel_name))
) || null;
return (
pendingPublishes.find(
pub =>
pub.outpoint === outpoint ||
(pub.name === name &&
(!channel_name || pub.channel_name === channel_name))
) || null
);
}
function pendingPublishToDummyClaim({channel_name, name, outpoint, claim_id, txid, nout}) {
return {name, outpoint, claim_id, txid, nout, channel_name};
function pendingPublishToDummyClaim({
channel_name,
name,
outpoint,
claim_id,
txid,
nout
}) {
return { name, outpoint, claim_id, txid, nout, channel_name };
}
function pendingPublishToDummyFileInfo({name, outpoint, claim_id}) {
return {name, outpoint, claim_id, metadata: null};
function pendingPublishToDummyFileInfo({ name, outpoint, claim_id }) {
return { name, outpoint, claim_id, metadata: null };
}
lbry.call = function (method, params, callback, errorCallback, connectFailedCallback) {
return jsonrpc.call(lbry.daemonConnectionString, method, params, callback, errorCallback, connectFailedCallback);
}
lbry.call = function(
method,
params,
callback,
errorCallback,
connectFailedCallback
) {
return jsonrpc.call(
lbry.daemonConnectionString,
method,
params,
callback,
errorCallback,
connectFailedCallback
);
};
//core
lbry._connectPromise = null;
lbry.connect = function() {
if (lbry._connectPromise === null) {
lbry._connectPromise = new Promise((resolve, reject) => {
let tryNum = 0
let tryNum = 0;
function checkDaemonStartedFailed() {
if (tryNum <= 100) { // Move # of tries into constant or config option
if (tryNum <= 100) {
// Move # of tries into constant or config option
setTimeout(() => {
tryNum++
tryNum++;
checkDaemonStarted();
}, tryNum < 50 ? 400 : 1000);
}
else {
reject(new Error("Unable to connect to LBRY"));
} else {
reject(new Error('Unable to connect to LBRY'));
}
}
// Check every half second to see if the daemon is accepting connections
function checkDaemonStarted() {
lbry.call('status', {}, resolve, checkDaemonStartedFailed, checkDaemonStartedFailed)
lbry.call(
'status',
{},
resolve,
checkDaemonStartedFailed,
checkDaemonStartedFailed
);
}
checkDaemonStarted();
@ -122,15 +162,20 @@ lbry.connect = function() {
}
return lbry._connectPromise;
}
};
lbry.checkAddressIsMine = function(address, callback) {
lbry.call('wallet_is_address_mine', {address: address}, callback);
}
lbry.call('wallet_is_address_mine', { address: address }, callback);
};
lbry.sendToAddress = function(amount, address, callback, errorCallback) {
lbry.call("send_amount_to_address", { "amount" : amount, "address": address }, callback, errorCallback);
}
lbry.call(
'send_amount_to_address',
{ amount: amount, address: address },
callback,
errorCallback
);
};
/**
* Takes a LBRY URI; will first try and calculate a total cost using
@ -142,17 +187,17 @@ lbry.sendToAddress = function(amount, address, callback, errorCallback) {
* - includes_data: Boolean; indicates whether or not the data fee info
* from Lighthouse is included.
*/
lbry.costPromiseCache = {}
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, {})
let costInfoCache = getSession(COST_INFO_CACHE_KEY, {});
function cacheAndResolve(cost, includesData) {
costInfoCache[uri] = {cost, includesData};
costInfoCache[uri] = { cost, includesData };
setSession(COST_INFO_CACHE_KEY, costInfoCache);
resolve({cost, includesData});
resolve({ cost, includesData });
}
if (!uri) {
@ -160,11 +205,13 @@ lbry.getCostInfo = function(uri) {
}
if (costInfoCache[uri] && costInfoCache[uri].cost) {
return resolve(costInfoCache[uri])
return resolve(costInfoCache[uri]);
}
function getCost(uri, size) {
lbry.stream_cost_estimate({uri, ... size !== null ? {size} : {}}).then((cost) => {
lbry
.stream_cost_estimate({ uri, ...(size !== null ? { size } : {}) })
.then(cost => {
cacheAndResolve(cost, size !== null);
}, reject);
}
@ -172,18 +219,17 @@ lbry.getCostInfo = function(uri) {
const uriObj = lbryuri.parse(uri);
const name = uriObj.path || uriObj.name;
lighthouse.get_size_for_name(name).then((size) => {
lighthouse.get_size_for_name(name).then(size => {
if (size) {
getCost(name, size);
}
else {
} else {
getCost(name, null);
}
})
});
});
}
return lbry.costPromiseCache[uri];
}
};
/**
* Publishes a file. The optional fileListedCallback is called when the file becomes available in
@ -192,22 +238,32 @@ lbry.getCostInfo = function(uri) {
* This currently includes a work-around to cache the file in local storage so that the pending
* publish can appear in the UI immediately.
*/
lbry.publish = function(params, fileListedCallback, publishedCallback, errorCallback) {
lbry.call('publish', params, (result) => {
lbry.publish = function(
params,
fileListedCallback,
publishedCallback,
errorCallback
) {
lbry.call(
'publish',
params,
result => {
if (returnedPending) {
return;
}
clearTimeout(returnPendingTimeout);
publishedCallback(result);
}, (err) => {
},
err => {
if (returnedPending) {
return;
}
clearTimeout(returnPendingTimeout);
errorCallback(err);
});
}
);
let returnedPending = false;
// Give a short grace period in case publish() returns right away or (more likely) gives an error
@ -215,75 +271,84 @@ lbry.publish = function(params, fileListedCallback, publishedCallback, errorCall
returnedPending = true;
if (publishedCallback) {
savePendingPublish({name: params.name, channel_name: params.channel_name});
savePendingPublish({
name: params.name,
channel_name: params.channel_name
});
publishedCallback(true);
}
if (fileListedCallback) {
const {name, channel_name} = params;
savePendingPublish({name: params.name, channel_name: params.channel_name});
const { name, channel_name } = params;
savePendingPublish({
name: params.name,
channel_name: params.channel_name
});
fileListedCallback(true);
}
}, 2000);
}
};
lbry.getClientSettings = function() {
var outSettings = {};
for (let setting of Object.keys(lbry.defaultClientSettings)) {
var localStorageVal = localStorage.getItem('setting_' + setting);
outSettings[setting] = (localStorageVal === null ? lbry.defaultClientSettings[setting] : JSON.parse(localStorageVal));
outSettings[setting] = localStorageVal === null
? lbry.defaultClientSettings[setting]
: JSON.parse(localStorageVal);
}
return outSettings;
}
};
lbry.getClientSetting = function(setting) {
var localStorageVal = localStorage.getItem('setting_' + setting);
if (setting == 'showDeveloperMenu')
{
if (setting == 'showDeveloperMenu') {
return true;
}
return (localStorageVal === null ? lbry.defaultClientSettings[setting] : JSON.parse(localStorageVal));
}
return localStorageVal === null
? lbry.defaultClientSettings[setting]
: JSON.parse(localStorageVal);
};
lbry.setClientSettings = function(settings) {
for (let setting of Object.keys(settings)) {
lbry.setClientSetting(setting, settings[setting]);
}
}
};
lbry.setClientSetting = function(setting, value) {
return localStorage.setItem('setting_' + setting, JSON.stringify(value));
}
};
lbry.getSessionInfo = function(callback) {
lbry.call('status', {session_status: true}, callback);
}
lbry.call('status', { session_status: true }, callback);
};
lbry.reportBug = function(message, callback) {
lbry.call('report_bug', {
lbry.call(
'report_bug',
{
message: message
}, callback);
}
},
callback
);
};
//utilities
lbry.formatCredits = function(amount, precision)
{
lbry.formatCredits = function(amount, precision) {
return amount.toFixed(precision || 1).replace(/\.?0+$/, '');
}
};
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.replace('/s+/g', '-');
name = name.toLowerCase().replace(/[^a-z0-9\-]/g, '');
return name;
}
};
lbry.imagePath = function(file)
{
lbry.imagePath = function(file) {
return 'img/' + file;
}
};
lbry.getMediaType = function(contentType, fileName) {
if (contentType) {
@ -307,7 +372,7 @@ lbry.getMediaType = function(contentType, fileName) {
} else {
return 'unknown';
}
}
};
lbry.stop = function(callback) {
lbry.call('stop', {}, callback);
@ -325,30 +390,38 @@ lbry._updateBalanceSubscribers = function() {
}
});
if (!lbry._balanceUpdateInterval && Object.keys(lbry._balanceSubscribeCallbacks).length) {
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)
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';
const chosenMenu = lbry.getClientSetting('showDeveloperMenu')
? 'developer'
: 'normal';
if (chosenMenu != showingMenu) {
menu.showMenubar(chosenMenu == 'developer');
}
@ -357,11 +430,12 @@ lbry.showMenuIfNeeded = function() {
lbry.getAppVersionInfo = function() {
return new Promise((resolve, reject) => {
ipcRenderer.once('version-info-received', (event, versionInfo) => { resolve(versionInfo) });
ipcRenderer.once('version-info-received', (event, versionInfo) => {
resolve(versionInfo);
});
ipcRenderer.send('version-info-requested');
});
}
};
/**
* Wrappers for API methods to simulate missing or future behavior. Unlike the old-style stubs,
@ -372,9 +446,9 @@ lbry.getAppVersionInfo = function() {
* Returns results from the file_list API method, plus dummy entries for pending publishes.
* (If a real publish with the same name is found, the pending publish will be ignored and removed.)
*/
lbry.file_list = function(params={}) {
lbry.file_list = function(params = {}) {
return new Promise((resolve, reject) => {
const {name, channel_name, outpoint} = params;
const { name, channel_name, outpoint } = params;
/**
* If we're searching by outpoint, check first to see if there's a matching pending publish.
@ -382,63 +456,88 @@ lbry.file_list = function(params={}) {
* to check if there's a real file.
*/
if (outpoint) {
const pendingPublish = getPendingPublish({outpoint});
const pendingPublish = getPendingPublish({ outpoint });
if (pendingPublish) {
resolve([pendingPublishToDummyFileInfo(pendingPublish)]);
return;
}
}
lbry.call('file_list', params, (fileInfos) => {
removePendingPublishIfNeeded({name, channel_name, outpoint});
lbry.call(
'file_list',
params,
fileInfos => {
removePendingPublishIfNeeded({ name, channel_name, outpoint });
const dummyFileInfos = lbry.getPendingPublishes().map(pendingPublishToDummyFileInfo);
const dummyFileInfos = lbry
.getPendingPublishes()
.map(pendingPublishToDummyFileInfo);
resolve([...fileInfos, ...dummyFileInfos]);
}, reject, reject);
},
reject,
reject
);
});
}
};
lbry.claim_list_mine = function(params={}) {
lbry.claim_list_mine = function(params = {}) {
return new Promise((resolve, reject) => {
lbry.call('claim_list_mine', params, (claims) => {
for (let {name, channel_name, txid, nout} of claims) {
removePendingPublishIfNeeded({name, channel_name, outpoint: txid + ':' + nout});
lbry.call(
'claim_list_mine',
params,
claims => {
for (let { name, channel_name, txid, nout } of claims) {
removePendingPublishIfNeeded({
name,
channel_name,
outpoint: txid + ':' + nout
});
}
const dummyClaims = lbry.getPendingPublishes().map(pendingPublishToDummyClaim);
const dummyClaims = lbry
.getPendingPublishes()
.map(pendingPublishToDummyClaim);
resolve([...claims, ...dummyClaims]);
}, reject, reject)
},
reject,
reject
);
});
}
};
const claimCacheKey = 'resolve_claim_cache';
lbry._claimCache = getSession(claimCacheKey, {});
lbry._resolveXhrs = {}
lbry.resolve = function(params={}) {
lbry._resolveXhrs = {};
lbry.resolve = function(params = {}) {
return new Promise((resolve, reject) => {
if (!params.uri) {
throw __("Resolve has hacked cache on top of it that requires a URI")
throw __('Resolve has hacked cache on top of it that requires a URI');
}
if (params.uri && lbry._claimCache[params.uri] !== undefined) {
resolve(lbry._claimCache[params.uri]);
} else {
lbry._resolveXhrs[params.uri] = lbry.call('resolve', params, function(data) {
lbry._resolveXhrs[params.uri] = lbry.call(
'resolve',
params,
function(data) {
if (data !== undefined) {
lbry._claimCache[params.uri] = data;
}
setSession(claimCacheKey, lbry._claimCache)
resolve(data)
}, reject)
setSession(claimCacheKey, lbry._claimCache);
resolve(data);
},
reject
);
}
});
}
};
lbry.cancelResolve = function(params={}) {
const xhr = lbry._resolveXhrs[params.uri]
lbry.cancelResolve = function(params = {}) {
const xhr = lbry._resolveXhrs[params.uri];
if (xhr && xhr.readyState > 0 && xhr.readyState < 4) {
xhr.abort()
xhr.abort();
}
}
};
lbry = new Proxy(lbry, {
get: function(target, name) {
@ -446,9 +545,16 @@ lbry = new Proxy(lbry, {
return target[name];
}
return function(params={}) {
return function(params = {}) {
return new Promise((resolve, reject) => {
jsonrpc.call(lbry.daemonConnectionString, name, params, resolve, reject, reject);
jsonrpc.call(
lbry.daemonConnectionString,
name,
params,
resolve,
reject,
reject
);
});
};
}

View file

@ -1,4 +1,4 @@
import {getSession, setSession} from './utils.js';
import { getSession, setSession } from './utils.js';
import lbry from './lbry.js';
const querystring = require('querystring');
@ -6,46 +6,63 @@ const querystring = require('querystring');
const lbryio = {
_accessToken: getSession('accessToken'),
_authenticationPromise: null,
_user : null,
_user: null,
enabled: true
};
const CONNECTION_STRING = process.env.LBRY_APP_API_URL ?
process.env.LBRY_APP_API_URL.replace(/\/*$/,'/') : // exactly one slash at the end
'https://api.lbry.io/'
const CONNECTION_STRING = process.env.LBRY_APP_API_URL
? process.env.LBRY_APP_API_URL.replace(/\/*$/, '/') // exactly one slash at the end
: 'https://api.lbry.io/';
const EXCHANGE_RATE_TIMEOUT = 20 * 60 * 1000;
lbryio._exchangePromise = null;
lbryio._exchangeLastFetched = null;
lbryio.getExchangeRates = function() {
if (!lbryio._exchangeLastFetched || Date.now() - lbryio._exchangeLastFetched > EXCHANGE_RATE_TIMEOUT) {
if (
!lbryio._exchangeLastFetched ||
Date.now() - lbryio._exchangeLastFetched > EXCHANGE_RATE_TIMEOUT
) {
lbryio._exchangePromise = new Promise((resolve, reject) => {
lbryio.call('lbc', 'exchange_rate', {}, 'get', true).then(({lbc_usd, lbc_btc, btc_usd}) => {
const rates = {lbc_usd, lbc_btc, btc_usd};
lbryio
.call('lbc', 'exchange_rate', {}, 'get', true)
.then(({ lbc_usd, lbc_btc, btc_usd }) => {
const rates = { lbc_usd, lbc_btc, btc_usd };
resolve(rates);
}).catch(reject);
})
.catch(reject);
});
lbryio._exchangeLastFetched = Date.now();
}
return lbryio._exchangePromise;
}
};
lbryio.call = function(resource, action, params={}, method='get', evenIfDisabled=false) { // evenIfDisabled is just for development, when we may have some calls working and some not
lbryio.call = function(
resource,
action,
params = {},
method = 'get',
evenIfDisabled = false
) {
// evenIfDisabled is just for development, when we may have some calls working and some not
return new Promise((resolve, reject) => {
if (!lbryio.enabled && !evenIfDisabled && (resource != 'discover' || action != 'list')) {
console.log(__("Internal API disabled"));
reject(new Error(__("LBRY internal API is disabled")))
return
if (
!lbryio.enabled &&
!evenIfDisabled &&
(resource != 'discover' || action != 'list')
) {
console.log(__('Internal API disabled'));
reject(new Error(__('LBRY internal API is disabled')));
return;
}
const xhr = new XMLHttpRequest;
const xhr = new XMLHttpRequest();
xhr.addEventListener('error', function (event) {
reject(new Error(__("Something went wrong making an internal API call.")));
xhr.addEventListener('error', function(event) {
reject(
new Error(__('Something went wrong making an internal API call.'))
);
});
xhr.addEventListener('timeout', function() {
reject(new Error(__('XMLHttpRequest connection timed out')));
});
@ -59,15 +76,17 @@ lbryio.call = function(resource, action, params={}, method='get', evenIfDisabled
error.xhr = xhr;
reject(error);
} else {
document.dispatchEvent(new CustomEvent('unhandledError', {
document.dispatchEvent(
new CustomEvent('unhandledError', {
detail: {
connectionString: connectionString,
method: action,
params: params,
message: response.error.message,
... response.error.data ? {data: response.error.data} : {},
...(response.error.data ? { data: response.error.data } : {})
}
}));
})
);
}
} else {
resolve(response.data);
@ -79,17 +98,26 @@ lbryio.call = function(resource, action, params={}, method='get', evenIfDisabled
//const fullParams = {...params, ... accessToken ? {access_token: accessToken} : {}};
// Temp app ID based auth:
const fullParams = {app_id: lbryio.getAccessToken(), ...params};
const fullParams = { app_id: lbryio.getAccessToken(), ...params };
if (method == 'get') {
xhr.open('get', CONNECTION_STRING + resource + '/' + action + '?' + querystring.stringify(fullParams), true);
xhr.open(
'get',
CONNECTION_STRING +
resource +
'/' +
action +
'?' +
querystring.stringify(fullParams),
true
);
xhr.send();
} else if (method == 'post') {
xhr.open('post', CONNECTION_STRING + resource + '/' + action, true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(querystring.stringify(fullParams));
} else {
reject(new Error(__("Invalid method")));
reject(new Error(__('Invalid method')));
}
});
};
@ -97,11 +125,11 @@ lbryio.call = function(resource, action, params={}, method='get', evenIfDisabled
lbryio.getAccessToken = () => {
const token = getSession('accessToken');
return token ? token.toString().trim() : token;
}
};
lbryio.setAccessToken = (token) => {
setSession('accessToken', token ? token.toString().trim() : token)
}
lbryio.setAccessToken = token => {
setSession('accessToken', token ? token.toString().trim() : token);
};
lbryio.authenticate = function() {
if (!lbryio.enabled) {
@ -109,55 +137,70 @@ lbryio.authenticate = function() {
resolve({
id: 1,
has_verified_email: true
})
})
});
});
}
if (lbryio._authenticationPromise === null) {
lbryio._authenticationPromise = new Promise((resolve, reject) => {
lbry.status().then((response) => {
lbry
.status()
.then(response => {
let installation_id = response.installation_id;
function setCurrentUser() {
lbryio.call('user', 'me').then((data) => {
lbryio.user = data
resolve(data)
}).catch(function(err) {
lbryio
.call('user', 'me')
.then(data => {
lbryio.user = data;
resolve(data);
})
.catch(function(err) {
lbryio.setAccessToken(null);
if (!getSession('reloadedOnFailedAuth')) {
setSession('reloadedOnFailedAuth', true)
setSession('reloadedOnFailedAuth', true);
window.location.reload();
} else {
reject(err);
}
})
});
}
if (!lbryio.getAccessToken()) {
lbryio.call('user', 'new', {
lbryio
.call(
'user',
'new',
{
language: 'en',
app_id: installation_id,
}, 'post').then(function(responseData) {
app_id: installation_id
},
'post'
)
.then(function(responseData) {
if (!responseData.id) {
reject(new Error(__("Received invalid authentication response.")));
reject(
new Error(__('Received invalid authentication response.'))
);
}
lbryio.setAccessToken(installation_id)
setCurrentUser()
}).catch(function(error) {
lbryio.setAccessToken(installation_id);
setCurrentUser();
})
.catch(function(error) {
/*
until we have better error code format, assume all errors are duplicate application id
if we're wrong, this will be caught by later attempts to make a valid call
*/
lbryio.setAccessToken(installation_id)
setCurrentUser()
})
lbryio.setAccessToken(installation_id);
setCurrentUser();
});
} else {
setCurrentUser()
setCurrentUser();
}
}).catch(reject);
})
.catch(reject);
});
}
return lbryio._authenticationPromise;
}
};
export default lbryio;

View file

@ -25,15 +25,18 @@ const lbryuri = {};
* - contentName (string): For anon claims, the name; for channel claims, the path
* - channelName (string, if present): Channel name without @
*/
lbryuri.parse = function(uri, requireProto=false) {
lbryuri.parse = function(uri, requireProto = false) {
// Break into components. Empty sub-matches are converted to null
const componentsRegex = new RegExp(
'^((?:lbry:\/\/)?)' + // protocol
'^((?:lbry://)?)' + // protocol
'([^:$#/]*)' + // name (stops at the first separator or end)
'([:$#]?)([^/]*)' + // modifier separator, modifier (stops at the first path separator or end)
'(/?)(.*)' // path separator, path
);
const [proto, name, modSep, modVal, pathSep, path] = componentsRegex.exec(uri).slice(1).map(match => match || null);
const [proto, name, modSep, modVal, pathSep, path] = componentsRegex
.exec(uri)
.slice(1)
.map(match => match || null);
let contentName;
@ -56,7 +59,12 @@ lbryuri.parse = function(uri, requireProto=false) {
}
if (channelName.length < CHANNEL_NAME_MIN_LEN) {
throw new Error(__(`Channel names must be at least %s characters.`, CHANNEL_NAME_MIN_LEN));
throw new Error(
__(
`Channel names must be at least %s characters.`,
CHANNEL_NAME_MIN_LEN
)
);
}
contentName = path;
@ -64,7 +72,13 @@ lbryuri.parse = function(uri, requireProto=false) {
const nameBadChars = (channelName || name).match(/[^A-Za-z0-9-]/g);
if (nameBadChars) {
throw new Error(__(`Invalid character %s in name: %s.`, nameBadChars.length == 1 ? '' : 's', nameBadChars.join(', ') ));
throw new Error(
__(
`Invalid character %s in name: %s.`,
nameBadChars.length == 1 ? '' : 's',
nameBadChars.join(', ')
)
);
}
// Validate and process modifier (claim ID, bid position or claim sequence)
@ -83,7 +97,10 @@ lbryuri.parse = function(uri, requireProto=false) {
}
}
if (claimId && (claimId.length > CLAIM_ID_MAX_LEN || !claimId.match(/^[0-9a-f]+$/))) {
if (
claimId &&
(claimId.length > CLAIM_ID_MAX_LEN || !claimId.match(/^[0-9a-f]+$/))
) {
throw new Error(__(`Invalid claim ID %s.`, claimId));
}
@ -103,7 +120,13 @@ lbryuri.parse = function(uri, requireProto=false) {
const pathBadChars = path.match(/[^A-Za-z0-9-]/g);
if (pathBadChars) {
throw new Error(__(`Invalid character %s in path: %s`,count == 1 ? '' : 's',nameBadChars.join(', ')));
throw new Error(
__(
`Invalid character %s in path: %s`,
count == 1 ? '' : 's',
nameBadChars.join(', ')
)
);
}
contentName = path;
@ -112,30 +135,46 @@ lbryuri.parse = function(uri, requireProto=false) {
}
return {
name, path, isChannel,
... contentName ? {contentName} : {},
... channelName ? {channelName} : {},
... claimSequence ? {claimSequence: parseInt(claimSequence)} : {},
... bidPosition ? {bidPosition: parseInt(bidPosition)} : {},
... claimId ? {claimId} : {},
... path ? {path} : {},
name,
path,
isChannel,
...(contentName ? { contentName } : {}),
...(channelName ? { channelName } : {}),
...(claimSequence ? { claimSequence: parseInt(claimSequence) } : {}),
...(bidPosition ? { bidPosition: parseInt(bidPosition) } : {}),
...(claimId ? { claimId } : {}),
...(path ? { path } : {})
};
}
};
/**
* Takes an object in the same format returned by lbryuri.parse() and builds a URI.
*
* The channelName key will accept names with or without the @ prefix.
*/
lbryuri.build = function(uriObj, includeProto=true, allowExtraProps=false) {
let {name, claimId, claimSequence, bidPosition, path, contentName, channelName} = uriObj;
lbryuri.build = function(uriObj, includeProto = true, allowExtraProps = false) {
let {
name,
claimId,
claimSequence,
bidPosition,
path,
contentName,
channelName
} = uriObj;
if (channelName) {
const channelNameFormatted = channelName.startsWith('@') ? channelName : '@' + channelName;
const channelNameFormatted = channelName.startsWith('@')
? channelName
: '@' + channelName;
if (!name) {
name = channelNameFormatted;
} else if (name !== channelNameFormatted) {
throw new Error(__('Received a channel content URI, but name and channelName do not match. \"name\" represents the value in the name position of the URI (lbry://name...), which for channel content will be the channel name. In most cases, to construct a channel URI you should just pass channelName and contentName.'));
throw new Error(
__(
'Received a channel content URI, but name and channelName do not match. "name" represents the value in the name position of the URI (lbry://name...), which for channel content will be the channel name. In most cases, to construct a channel URI you should just pass channelName and contentName.'
)
);
}
}
@ -146,49 +185,65 @@ lbryuri.build = function(uriObj, includeProto=true, allowExtraProps=false) {
path = contentName;
}
if (path && path !== contentName) {
throw new Error(__('Path and contentName do not match. Only one is required; most likely you wanted contentName.'));
throw new Error(
__(
'Path and contentName do not match. Only one is required; most likely you wanted contentName.'
)
);
}
}
return (includeProto ? 'lbry://' : '') + name +
return (
(includeProto ? 'lbry://' : '') +
name +
(claimId ? `#${claimId}` : '') +
(claimSequence ? `:${claimSequence}` : '') +
(bidPosition ? `\$${bidPosition}` : '') +
(path ? `/${path}` : '');
}
(path ? `/${path}` : '')
);
};
/* Takes a parseable LBRY URI and converts it to standard, canonical format (currently this just
* consists of adding the lbry:// prefix if needed) */
lbryuri.normalize= function(uri) {
const {name, path, bidPosition, claimSequence, claimId} = lbryuri.parse(uri);
return lbryuri.build({name, path, claimSequence, bidPosition, claimId});
}
lbryuri.normalize = function(uri) {
const { name, path, bidPosition, claimSequence, claimId } = lbryuri.parse(
uri
);
return lbryuri.build({ name, path, claimSequence, bidPosition, claimId });
};
lbryuri.isValid = function(uri) {
let parts
let parts;
try {
parts = lbryuri.parse(lbryuri.normalize(uri))
parts = lbryuri.parse(lbryuri.normalize(uri));
} catch (error) {
return false;
}
return parts && parts.name;
}
};
lbryuri.isValidName = function(name, checkCase=true) {
lbryuri.isValidName = function(name, checkCase = true) {
const regexp = new RegExp('^[a-z0-9-]+$', checkCase ? '' : 'i');
return regexp.test(name);
}
};
lbryuri.isClaimable = function(uri) {
let parts
let parts;
try {
parts = lbryuri.parse(lbryuri.normalize(uri))
parts = lbryuri.parse(lbryuri.normalize(uri));
} catch (error) {
return false;
}
return parts && parts.name && !parts.claimId && !parts.bidPosition && !parts.claimSequence && !parts.isChannel && !parts.path;
}
return (
parts &&
parts.name &&
!parts.claimId &&
!parts.bidPosition &&
!parts.claimSequence &&
!parts.isChannel &&
!parts.path
);
};
window.lbryuri = lbryuri;
export default lbryuri;

View file

@ -6,7 +6,7 @@ const maxQueryTries = 2;
const defaultServers = [
'http://lighthouse7.lbry.io:50005',
'http://lighthouse8.lbry.io:50005',
'http://lighthouse9.lbry.io:50005',
'http://lighthouse9.lbry.io:50005'
];
const path = '/';
@ -21,7 +21,14 @@ function getServers() {
function call(method, params, callback, errorCallback) {
if (connectTryNum >= maxQueryTries) {
errorCallback(new Error(__(`Could not connect to Lighthouse server. Last server attempted: %s`, server)));
errorCallback(
new Error(
__(
`Could not connect to Lighthouse server. Last server attempted: %s`,
server
)
)
);
return;
}
@ -32,30 +39,46 @@ function call(method, params, callback, errorCallback) {
*/
if (!server || !getServers().includes(server) || connectTryNum > 0) {
// If there's a current server, filter it out so we get a new one
const newServerChoices = server ? getServers().filter((s) => s != server) : getServers();
server = newServerChoices[Math.round(Math.random() * (newServerChoices.length - 1))];
const newServerChoices = server
? getServers().filter(s => s != server)
: getServers();
server =
newServerChoices[
Math.round(Math.random() * (newServerChoices.length - 1))
];
}
jsonrpc.call(server + path, method, params, (response) => {
jsonrpc.call(
server + path,
method,
params,
response => {
connectTryNum = 0;
callback(response);
}, (error) => {
},
error => {
connectTryNum = 0;
errorCallback(error);
}, () => {
},
() => {
connectTryNum++;
call(method, params, callback, errorCallback);
}, queryTimeout);
},
queryTimeout
);
}
const lighthouse = new Proxy({}, {
const lighthouse = new Proxy(
{},
{
get: function(target, name) {
return function(...params) {
return new Promise((resolve, reject) => {
call(name, params, resolve, reject);
});
};
},
});
}
}
);
export default lighthouse;

View file

@ -8,58 +8,51 @@ import SnackBar from 'component/snackBar';
import { Provider } from 'react-redux';
import store from 'store.js';
import SplashScreen from 'component/splash.js';
import {AuthOverlay} from 'component/auth.js';
import {
doChangePath,
doNavigate,
doDaemonReady
} from 'actions/app'
import {
doFetchDaemonSettings
} from 'actions/settings'
import {
doFileList
} from 'actions/file_info'
import {
toQueryString,
} from 'util/query_params'
import { AuthOverlay } from 'component/auth.js';
import { doChangePath, doNavigate, doDaemonReady } from 'actions/app';
import { doFetchDaemonSettings } from 'actions/settings';
import { doFileList } from 'actions/file_info';
import { toQueryString } from 'util/query_params';
const {remote, ipcRenderer, shell} = require('electron');
const { remote, ipcRenderer, shell } = require('electron');
const contextMenu = remote.require('./menu/context-menu');
const app = require('./app')
const app = require('./app');
lbry.showMenuIfNeeded();
window.addEventListener('contextmenu', (event) => {
contextMenu.showContextMenu(remote.getCurrentWindow(), event.x, event.y,
lbry.getClientSetting('showDeveloperMenu'));
window.addEventListener('contextmenu', event => {
contextMenu.showContextMenu(
remote.getCurrentWindow(),
event.x,
event.y,
lbry.getClientSetting('showDeveloperMenu')
);
event.preventDefault();
});
window.addEventListener('popstate', (event, param) => {
const params = event.state
const pathParts = document.location.pathname.split('/')
const route = '/' + pathParts[pathParts.length - 1]
const queryString = toQueryString(params)
const params = event.state;
const pathParts = document.location.pathname.split('/');
const route = '/' + pathParts[pathParts.length - 1];
const queryString = toQueryString(params);
let action
let action;
if (route.match(/html$/)) {
action = doChangePath('/discover')
action = doChangePath('/discover');
} else {
action = doChangePath(`${route}?${queryString}`)
action = doChangePath(`${route}?${queryString}`);
}
app.store.dispatch(action)
})
app.store.dispatch(action);
});
ipcRenderer.on('open-uri-requested', (event, uri) => {
if (uri && uri.startsWith('lbry://')) {
app.store.dispatch(doNavigate('/show', { uri }))
app.store.dispatch(doNavigate('/show', { uri }));
}
});
document.addEventListener('click', (event) => {
document.addEventListener('click', event => {
var target = event.target;
while (target && target !== document) {
if (target.matches('a[href^="http"]')) {
@ -74,17 +67,21 @@ document.addEventListener('click', (event) => {
const initialState = app.store.getState();
var init = function() {
function onDaemonReady() {
window.sessionStorage.setItem('loaded', 'y'); //once we've made it here once per session, we don't need to show splash again
const actions = []
const actions = [];
app.store.dispatch(doDaemonReady())
app.store.dispatch(doChangePath('/discover'))
app.store.dispatch(doFetchDaemonSettings())
app.store.dispatch(doFileList())
app.store.dispatch(doDaemonReady());
app.store.dispatch(doChangePath('/discover'));
app.store.dispatch(doFetchDaemonSettings());
app.store.dispatch(doFileList());
ReactDOM.render(<Provider store={store}><div>{ lbryio.enabled ? <AuthOverlay/> : '' }<App /><SnackBar /></div></Provider>, canvas)
ReactDOM.render(
<Provider store={store}>
<div>{lbryio.enabled ? <AuthOverlay /> : ''}<App /><SnackBar /></div>
</Provider>,
canvas
);
}
if (window.sessionStorage.getItem('loaded') == 'y') {

View file

@ -1,30 +1,26 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
doFetchClaimsByChannel
} from 'actions/content'
import React from 'react';
import { connect } from 'react-redux';
import { doFetchClaimsByChannel } from 'actions/content';
import {
makeSelectClaimForUri,
makeSelectClaimsInChannelForUri
} from 'selectors/claims'
import ChannelPage from './view'
} from 'selectors/claims';
import ChannelPage from './view';
const makeSelect = () => {
const selectClaim = makeSelectClaimForUri(),
selectClaimsInChannel = makeSelectClaimsInChannelForUri()
selectClaimsInChannel = makeSelectClaimsInChannelForUri();
const select = (state, props) => ({
claim: selectClaim(state, props),
claimsInChannel: selectClaimsInChannel(state, props)
})
});
return select
}
return select;
};
const perform = (dispatch) => ({
fetchClaims: (uri) => dispatch(doFetchClaimsByChannel(uri))
})
const perform = dispatch => ({
fetchClaims: uri => dispatch(doFetchClaimsByChannel(uri))
});
export default connect(makeSelect, perform)(ChannelPage)
export default connect(makeSelect, perform)(ChannelPage);

View file

@ -1,53 +1,56 @@
import React from 'react';
import lbryuri from 'lbryuri'
import {BusyMessage} from 'component/common'
import FileTile from 'component/fileTile'
import lbryuri from 'lbryuri';
import { BusyMessage } from 'component/common';
import FileTile from 'component/fileTile';
class ChannelPage extends React.Component{
class ChannelPage extends React.Component {
componentDidMount() {
this.fetchClaims(this.props)
this.fetchClaims(this.props);
}
componentWillReceiveProps(nextProps) {
this.fetchClaims(nextProps)
this.fetchClaims(nextProps);
}
fetchClaims(props) {
if (props.claimsInChannel === undefined) {
props.fetchClaims(props.uri)
props.fetchClaims(props.uri);
}
}
render() {
const {
claimsInChannel,
claim,
uri
} = this.props
const { claimsInChannel, claim, uri } = this.props;
let contentList
let contentList;
if (claimsInChannel === undefined) {
contentList = <BusyMessage message={__("Fetching content")} />
contentList = <BusyMessage message={__('Fetching content')} />;
} else if (claimsInChannel) {
contentList = claimsInChannel.length ?
claimsInChannel.map((claim) => <FileTile key={claim.claim_id} uri={lbryuri.build({name: claim.name, claimId: claim.claim_id})} />) :
<span className="empty">{__("No content found.")}</span>
contentList = claimsInChannel.length
? claimsInChannel.map(claim =>
<FileTile
key={claim.claim_id}
uri={lbryuri.build({ name: claim.name, claimId: claim.claim_id })}
/>
)
: <span className="empty">{__('No content found.')}</span>;
}
return <main className="main--single-column">
return (
<main className="main--single-column">
<section className="card">
<div className="card__inner">
<div className="card__title-identity"><h1>{uri}</h1></div>
</div>
<div className="card__content">
<p>
{__("This channel page is a stub.")}
{__('This channel page is a stub.')}
</p>
</div>
</section>
<h3 className="card-row__header">{__("Published Content")}</h3>
<h3 className="card-row__header">{__('Published Content')}</h3>
{contentList}
</main>
);
}
}

View file

@ -1,10 +1,10 @@
import lbry from '../lbry.js';
import React from 'react';
import {FormField} from '../component/form.js';
import { FormField } from '../component/form.js';
import Link from '../component/link';
const fs = require('fs');
const {ipcRenderer} = require('electron');
const { ipcRenderer } = require('electron');
class DeveloperPage extends React.Component {
constructor(props) {
@ -12,9 +12,13 @@ class DeveloperPage extends React.Component {
this.state = {
showDeveloperMenu: lbry.getClientSetting('showDeveloperMenu'),
useCustomLighthouseServers: lbry.getClientSetting('useCustomLighthouseServers'),
customLighthouseServers: lbry.getClientSetting('customLighthouseServers').join('\n'),
upgradePath: '',
useCustomLighthouseServers: lbry.getClientSetting(
'useCustomLighthouseServers'
),
customLighthouseServers: lbry
.getClientSetting('customLighthouseServers')
.join('\n'),
upgradePath: ''
};
}
@ -22,20 +26,20 @@ class DeveloperPage extends React.Component {
lbry.setClientSetting('showDeveloperMenu', event.target.checked);
lbry.showMenuIfNeeded();
this.setState({
showDeveloperMenu: event.target.checked,
showDeveloperMenu: event.target.checked
});
}
handleUseCustomLighthouseServersChange(event) {
lbry.setClientSetting('useCustomLighthouseServers', event.target.checked);
this.setState({
useCustomLighthouseServers: event.target.checked,
useCustomLighthouseServers: event.target.checked
});
}
handleUpgradeFileChange(event) {
this.setState({
upgradePath: event.target.value,
upgradePath: event.target.value
});
}
@ -51,10 +55,13 @@ class DeveloperPage extends React.Component {
ipcRenderer.send('upgrade', this.state.upgradePath);
upgradeSent = true;
}
}
catch (e) {}
} catch (e) {}
if (!upgradeSent) {
alert('Failed to start upgrade. Is "' + this.state.upgradePath + '" a valid path to the upgrade?');
alert(
'Failed to start upgrade. Is "' +
this.state.upgradePath +
'" a valid path to the upgrade?'
);
}
}
}
@ -63,27 +70,70 @@ class DeveloperPage extends React.Component {
return (
<main>
<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>
<label>
<FormField
type="checkbox"
onChange={event => {
this.handleShowDeveloperMenuChange();
}}
checked={this.state.showDeveloperMenu}
/>
{' '}
{__('Show developer menu')}
</label>
</div>
<div className="form-row">
<label><FormField type="checkbox" onChange={(event) => { this.handleUseCustomLighthouseServersChange() }} checked={this.state.useCustomLighthouseServers} /> {__("Use custom search servers")}</label>
<label>
<FormField
type="checkbox"
onChange={event => {
this.handleUseCustomLighthouseServersChange();
}}
checked={this.state.useCustomLighthouseServers}
/>
{' '}
{__('Use custom search servers')}
</label>
</div>
{this.state.useCustomLighthouseServers
? <div className="form-row">
<label>
{__("Custom search servers (one per line)")}
<div><FormField type="textarea" className="developer-page__custom-lighthouse-servers" value={this.state.customLighthouseServers} onChange={(event) => { this.handleCustomLighthouseServersChange() }} checked={this.state.debugMode} /></div>
{__('Custom search servers (one per line)')}
<div>
<FormField
type="textarea"
className="developer-page__custom-lighthouse-servers"
value={this.state.customLighthouseServers}
onChange={event => {
this.handleCustomLighthouseServersChange();
}}
checked={this.state.debugMode}
/>
</div>
</label>
</div>
: null}
</section>
<section className="card">
<div className="form-row">
<FormField name="file" ref="file" type="file" onChange={(event) => { this.handleUpgradeFileChange() }}/>
<FormField
name="file"
ref="file"
type="file"
onChange={event => {
this.handleUpgradeFileChange();
}}
/>
&nbsp;
<Link label={__("Force Upgrade")} button="alt" onClick={(event) => { this.handleForceUpgradeClick() }} />
<Link
label={__('Force Upgrade')}
button="alt"
onClick={event => {
this.handleForceUpgradeClick();
}}
/>
</div>
</section>
</main>

View file

@ -1,23 +1,19 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
doFetchFeaturedUris,
} from 'actions/content'
import React from 'react';
import { connect } from 'react-redux';
import { doFetchFeaturedUris } from 'actions/content';
import {
selectFeaturedUris,
selectFetchingFeaturedUris,
} from 'selectors/content'
import DiscoverPage from './view'
selectFetchingFeaturedUris
} from 'selectors/content';
import DiscoverPage from './view';
const select = (state) => ({
const select = state => ({
featuredUris: selectFeaturedUris(state),
fetchingFeaturedUris: selectFetchingFeaturedUris(state),
})
fetchingFeaturedUris: selectFetchingFeaturedUris(state)
});
const perform = (dispatch) => ({
const perform = dispatch => ({
fetchFeaturedUris: () => dispatch(doFetchFeaturedUris())
})
});
export default connect(select, perform)(DiscoverPage)
export default connect(select, perform)(DiscoverPage);

View file

@ -1,61 +1,73 @@
import React from 'react';
import lbryio from 'lbryio.js';
import lbryuri from 'lbryuri'
import lbryuri from 'lbryuri';
import FileCard from 'component/fileCard';
import {BusyMessage} from 'component/common.js';
import { BusyMessage } from 'component/common.js';
import ToolTip from 'component/tooltip.js';
const communityCategoryToolTipText = ('Community Content is a public space where anyone can share content with the ' +
const communityCategoryToolTipText =
'Community Content is a public space where anyone can share content with the ' +
'rest of the LBRY community. Bid on the names "one," "two," "three," "four" and ' +
'"five" to put your content here!');
'"five" to put your content here!';
const FeaturedCategory = (props) => {
const {
category,
names,
} = props
const FeaturedCategory = props => {
const { category, names } = props;
return <div className="card-row card-row--small">
<h3 className="card-row__header">{category}
{category && category.match(/^community/i) && <ToolTip label={__("What's this?")} body={__(communityCategoryToolTipText)} className="tooltip--header" />}
return (
<div className="card-row card-row--small">
<h3 className="card-row__header">
{category}
{category &&
category.match(/^community/i) &&
<ToolTip
label={__("What's this?")}
body={__(communityCategoryToolTipText)}
className="tooltip--header"
/>}
</h3>
{names && names.map(name => <FileCard key={name} displayStyle="card" uri={lbryuri.normalize(name)} />)}
{names &&
names.map(name =>
<FileCard
key={name}
displayStyle="card"
uri={lbryuri.normalize(name)}
/>
)}
</div>
}
);
};
class DiscoverPage extends React.Component{
class DiscoverPage extends React.Component {
componentWillMount() {
this.props.fetchFeaturedUris()
this.props.fetchFeaturedUris();
}
render() {
const {
featuredUris,
fetchingFeaturedUris,
} = this.props
const failedToLoad = !fetchingFeaturedUris && (
featuredUris === undefined ||
(featuredUris !== undefined && Object.keys(featuredUris).length === 0)
)
const { featuredUris, fetchingFeaturedUris } = this.props;
const failedToLoad =
!fetchingFeaturedUris &&
(featuredUris === undefined ||
(featuredUris !== undefined && Object.keys(featuredUris).length === 0));
return (
<main>
{
fetchingFeaturedUris &&
<BusyMessage message={__("Fetching content")} />
}
{
typeof featuredUris === "object" &&
Object.keys(featuredUris).map(category => (
featuredUris[category].length ? <FeaturedCategory key={category} category={category} names={featuredUris[category]} /> : ''
))
}
{
failedToLoad &&
<div className="empty">{__("Failed to load landing content.")}</div>
}
{fetchingFeaturedUris &&
<BusyMessage message={__('Fetching content')} />}
{typeof featuredUris === 'object' &&
Object.keys(featuredUris).map(
category =>
featuredUris[category].length
? <FeaturedCategory
key={category}
category={category}
names={featuredUris[category]}
/>
: ''
)}
{failedToLoad &&
<div className="empty">{__('Failed to load landing content.')}</div>}
</main>
)
);
}
}

View file

@ -1,27 +1,21 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
doFetchFileInfosAndPublishedClaims,
} from 'actions/file_info'
import React from 'react';
import { connect } from 'react-redux';
import { doFetchFileInfosAndPublishedClaims } from 'actions/file_info';
import {
selectFileInfosDownloaded,
selectFileListDownloadedOrPublishedIsPending,
} from 'selectors/file_info'
import {
doNavigate,
} from 'actions/app'
import FileListDownloaded from './view'
selectFileListDownloadedOrPublishedIsPending
} from 'selectors/file_info';
import { doNavigate } from 'actions/app';
import FileListDownloaded from './view';
const select = (state) => ({
const select = state => ({
fileInfos: selectFileInfosDownloaded(state),
isPending: selectFileListDownloadedOrPublishedIsPending(state),
})
isPending: selectFileListDownloadedOrPublishedIsPending(state)
});
const perform = (dispatch) => ({
navigate: (path) => dispatch(doNavigate(path)),
fetchFileInfosDownloaded: () => dispatch(doFetchFileInfosAndPublishedClaims()),
})
const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)),
fetchFileInfosDownloaded: () => dispatch(doFetchFileInfosAndPublishedClaims())
});
export default connect(select, perform)(FileListDownloaded)
export default connect(select, perform)(FileListDownloaded);

View file

@ -2,34 +2,38 @@ import React from 'react';
import lbry from 'lbry.js';
import lbryuri from 'lbryuri.js';
import Link from 'component/link';
import {FormField} from 'component/form.js';
import {FileTile} from 'component/fileTile';
import { FormField } from 'component/form.js';
import { FileTile } from 'component/fileTile';
import rewards from 'rewards.js';
import lbryio from 'lbryio.js';
import {BusyMessage, Thumbnail} from 'component/common.js';
import FileList from 'component/fileList'
import SubHeader from 'component/subHeader'
import { BusyMessage, Thumbnail } from 'component/common.js';
import FileList from 'component/fileList';
import SubHeader from 'component/subHeader';
class FileListDownloaded extends React.Component {
componentWillMount() {
this.props.fetchFileInfosDownloaded()
this.props.fetchFileInfosDownloaded();
}
render() {
const {
fileInfos,
isPending,
navigate,
} = this.props
const { fileInfos, isPending, navigate } = this.props;
let content
let content;
if (fileInfos && fileInfos.length > 0) {
content = <FileList fileInfos={fileInfos} fetching={isPending} />
content = <FileList fileInfos={fileInfos} fetching={isPending} />;
} else {
if (isPending) {
content = <BusyMessage message={__("Loading")} />
content = <BusyMessage message={__('Loading')} />;
} else {
content = <span>{__("You haven't downloaded anything from LBRY yet. Go")} <Link onClick={() => navigate('/discover')} label={__("search for your first download")} />!</span>
content = (
<span>
{__("You haven't downloaded anything from LBRY yet. Go")}
{' '}<Link
onClick={() => navigate('/discover')}
label={__('search for your first download')}
/>!
</span>
);
}
}
@ -38,8 +42,8 @@ class FileListDownloaded extends React.Component {
<SubHeader />
{content}
</main>
)
);
}
}
export default FileListDownloaded
export default FileListDownloaded;

View file

@ -1,27 +1,21 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
doFetchFileInfosAndPublishedClaims,
} from 'actions/file_info'
import React from 'react';
import { connect } from 'react-redux';
import { doFetchFileInfosAndPublishedClaims } from 'actions/file_info';
import {
selectFileInfosPublished,
selectFileListDownloadedOrPublishedIsPending
} from 'selectors/file_info'
import {
doNavigate,
} from 'actions/app'
import FileListPublished from './view'
} from 'selectors/file_info';
import { doNavigate } from 'actions/app';
import FileListPublished from './view';
const select = (state) => ({
const select = state => ({
fileInfos: selectFileInfosPublished(state),
isPending: selectFileListDownloadedOrPublishedIsPending(state),
})
isPending: selectFileListDownloadedOrPublishedIsPending(state)
});
const perform = (dispatch) => ({
navigate: (path) => dispatch(doNavigate(path)),
fetchFileListPublished: () => dispatch(doFetchFileInfosAndPublishedClaims()),
})
const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)),
fetchFileListPublished: () => dispatch(doFetchFileInfosAndPublishedClaims())
});
export default connect(select, perform)(FileListPublished)
export default connect(select, perform)(FileListPublished);

View file

@ -2,21 +2,21 @@ import React from 'react';
import lbry from 'lbry.js';
import lbryuri from 'lbryuri.js';
import Link from 'component/link';
import {FormField} from 'component/form.js';
import { FormField } from 'component/form.js';
import FileTile from 'component/fileTile';
import rewards from 'rewards.js';
import lbryio from 'lbryio.js';
import {BusyMessage, Thumbnail} from 'component/common.js';
import FileList from 'component/fileList'
import SubHeader from 'component/subHeader'
import { BusyMessage, Thumbnail } from 'component/common.js';
import FileList from 'component/fileList';
import SubHeader from 'component/subHeader';
class FileListPublished extends React.Component {
componentWillMount() {
this.props.fetchFileListPublished()
this.props.fetchFileListPublished();
}
componentDidUpdate() {
if(this.props.fileInfos.length > 0) this._requestPublishReward()
if (this.props.fileInfos.length > 0) this._requestPublishReward();
}
_requestPublishReward() {
@ -37,21 +37,31 @@ class FileListPublished extends React.Component {
}
render() {
const {
fileInfos,
isPending,
navigate,
} = this.props
const { fileInfos, isPending, navigate } = this.props;
let content
let content;
if (fileInfos && fileInfos.length > 0) {
content = <FileList fileInfos={fileInfos} fetching={isPending} fileTileShowEmpty={FileTile.SHOW_EMPTY_PENDING} />
content = (
<FileList
fileInfos={fileInfos}
fetching={isPending}
fileTileShowEmpty={FileTile.SHOW_EMPTY_PENDING}
/>
);
} else {
if (isPending) {
content = <BusyMessage message={__("Loading")} />
content = <BusyMessage message={__('Loading')} />;
} else {
content = <span>{__("It looks like you haven't published anything to LBRY yet. Go")} <Link onClick={() => navigate('/publish')} label={__("share your beautiful cats with the world")} />!</span>
content = (
<span>
{__("It looks like you haven't published anything to LBRY yet. Go")}
{' '}<Link
onClick={() => navigate('/publish')}
label={__('share your beautiful cats with the world')}
/>!
</span>
);
}
}
@ -60,8 +70,8 @@ class FileListPublished extends React.Component {
<SubHeader />
{content}
</main>
)
);
}
}
export default FileListPublished
export default FileListPublished;

View file

@ -1,35 +1,23 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
doNavigate,
} from 'actions/app'
import {
doFetchFileInfo,
} from 'actions/file_info'
import {
makeSelectFileInfoForUri,
} from 'selectors/file_info'
import {
doFetchCostInfoForUri,
} from 'actions/cost_info'
import React from 'react';
import { connect } from 'react-redux';
import { doNavigate } from 'actions/app';
import { doFetchFileInfo } from 'actions/file_info';
import { makeSelectFileInfoForUri } from 'selectors/file_info';
import { doFetchCostInfoForUri } from 'actions/cost_info';
import {
makeSelectClaimForUri,
makeSelectContentTypeForUri,
makeSelectMetadataForUri,
} from 'selectors/claims'
import {
makeSelectCostInfoForUri,
} from 'selectors/cost_info'
import FilePage from './view'
makeSelectMetadataForUri
} from 'selectors/claims';
import { makeSelectCostInfoForUri } from 'selectors/cost_info';
import FilePage from './view';
const makeSelect = () => {
const selectClaim = makeSelectClaimForUri(),
selectContentType = makeSelectContentTypeForUri(),
selectFileInfo = makeSelectFileInfoForUri(),
selectCostInfo = makeSelectCostInfoForUri(),
selectMetadata = makeSelectMetadataForUri()
selectMetadata = makeSelectMetadataForUri();
const select = (state, props) => ({
claim: selectClaim(state, props),
@ -37,15 +25,15 @@ const makeSelect = () => {
costInfo: selectCostInfo(state, props),
metadata: selectMetadata(state, props),
fileInfo: selectFileInfo(state, props)
})
});
return select
}
return select;
};
const perform = (dispatch) => ({
const perform = dispatch => ({
navigate: (path, params) => dispatch(doNavigate(path, params)),
fetchFileInfo: (uri) => dispatch(doFetchFileInfo(uri)),
fetchCostInfo: (uri) => dispatch(doFetchCostInfoForUri(uri)),
})
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri))
});
export default connect(makeSelect, perform)(FilePage)
export default connect(makeSelect, perform)(FilePage);

View file

@ -1,24 +1,15 @@
import React from 'react';
import lbry from 'lbry.js';
import lbryuri from 'lbryuri.js';
import Video from 'component/video'
import {
Thumbnail,
} from 'component/common';
import FilePrice from 'component/filePrice'
import Video from 'component/video';
import { Thumbnail } from 'component/common';
import FilePrice from 'component/filePrice';
import FileActions from 'component/fileActions';
import Link from 'component/link';
import UriIndicator from 'component/uriIndicator';
const FormatItem = (props) => {
const {
contentType,
metadata: {
author,
language,
license,
}
} = props
const FormatItem = props => {
const { contentType, metadata: { author, language, license } } = props;
const mediaType = lbry.getMediaType(contentType);
@ -26,56 +17,51 @@ const FormatItem = (props) => {
<table className="table-standard">
<tbody>
<tr>
<td>{__("Content-Type")}</td><td>{mediaType}</td>
<td>{__('Content-Type')}</td><td>{mediaType}</td>
</tr>
<tr>
<td>{__("Author")}</td><td>{author}</td>
<td>{__('Author')}</td><td>{author}</td>
</tr>
<tr>
<td>{__("Language")}</td><td>{language}</td>
<td>{__('Language')}</td><td>{language}</td>
</tr>
<tr>
<td>{__("License")}</td><td>{license}</td>
<td>{__('License')}</td><td>{license}</td>
</tr>
</tbody>
</table>
)
}
class FilePage extends React.Component{
);
};
class FilePage extends React.Component {
componentDidMount() {
this.fetchFileInfo(this.props)
this.fetchCostInfo(this.props)
this.fetchFileInfo(this.props);
this.fetchCostInfo(this.props);
}
componentWillReceiveProps(nextProps) {
this.fetchFileInfo(nextProps)
this.fetchFileInfo(nextProps);
}
fetchFileInfo(props) {
if (props.fileInfo === undefined) {
props.fetchFileInfo(props.uri)
props.fetchFileInfo(props.uri);
}
}
fetchCostInfo(props) {
if (props.costInfo === undefined) {
props.fetchCostInfo(props.uri)
props.fetchCostInfo(props.uri);
}
}
render() {
const {
claim,
fileInfo,
metadata,
contentType,
uri,
} = this.props
const { claim, fileInfo, metadata, contentType, uri } = this.props;
if (!claim || !metadata) {
return <span className="empty">{__("Empty claim or metadata info.")}</span>
return (
<span className="empty">{__('Empty claim or metadata info.')}</span>
);
}
const {
@ -85,35 +71,50 @@ class FilePage extends React.Component{
has_signature: hasSignature,
signature_is_valid: signatureIsValid,
value
} = claim
} = claim;
const outpoint = txid + ':' + nout
const title = metadata.title
const channelClaimId = claim.value && claim.value.publisherSignature ? claim.value.publisherSignature.certificateId : null;
const channelUri = signatureIsValid && hasSignature && channelName ? lbryuri.build({channelName, claimId: channelClaimId}, false) : null
const uriIndicator = <UriIndicator uri={uri} />
const mediaType = lbry.getMediaType(contentType)
const player = require('render-media')
const isPlayable = Object.values(player.mime).indexOf(contentType) !== -1 ||
mediaType === "audio"
const outpoint = txid + ':' + nout;
const title = metadata.title;
const channelClaimId = claim.value && claim.value.publisherSignature
? claim.value.publisherSignature.certificateId
: null;
const channelUri = signatureIsValid && hasSignature && channelName
? lbryuri.build({ channelName, claimId: channelClaimId }, false)
: null;
const uriIndicator = <UriIndicator uri={uri} />;
const mediaType = lbry.getMediaType(contentType);
const player = require('render-media');
const isPlayable =
Object.values(player.mime).indexOf(contentType) !== -1 ||
mediaType === 'audio';
return (
<main className="main--single-column">
<section className="show-page-media">
{ isPlayable ?
<Video className="video-embedded" uri={uri} /> :
(metadata && metadata.thumbnail ? <Thumbnail src={metadata.thumbnail} /> : <Thumbnail />) }
{isPlayable
? <Video className="video-embedded" uri={uri} />
: metadata && metadata.thumbnail
? <Thumbnail src={metadata.thumbnail} />
: <Thumbnail />}
</section>
<section className="card">
<div className="card__inner">
<div className="card__title-identity">
{!fileInfo || fileInfo.written_bytes <= 0
? <span style={{float: "right"}}><FilePrice uri={lbryuri.normalize(uri)} /></span>
: null}<h1>{title}</h1>
? <span style={{ float: 'right' }}>
<FilePrice uri={lbryuri.normalize(uri)} />
</span>
: null}
<h1>{title}</h1>
<div className="card__subtitle">
{ channelUri ?
<Link onClick={() => this.props.navigate('/show', { uri: channelUri })}>{uriIndicator}</Link> :
uriIndicator}
{channelUri
? <Link
onClick={() =>
this.props.navigate('/show', { uri: channelUri })}
>
{uriIndicator}
</Link>
: uriIndicator}
</div>
<div className="card__actions">
<FileActions uri={uri} />
@ -123,16 +124,21 @@ class FilePage extends React.Component{
{metadata && metadata.description}
</div>
</div>
{ metadata ?
<div className="card__content">
{metadata
? <div className="card__content">
<FormatItem metadata={metadata} contentType={contentType} />
</div> : '' }
</div>
: ''}
<div className="card__content">
<Link href="https://lbry.io/dmca" label={__("report")} className="button-text-help" />
<Link
href="https://lbry.io/dmca"
label={__('report')}
className="button-text-help"
/>
</div>
</section>
</main>
)
);
}
}

View file

@ -1,14 +1,10 @@
import React from 'react'
import {
doNavigate
} from 'actions/app'
import {
connect
} from 'react-redux'
import HelpPage from './view'
import React from 'react';
import { doNavigate } from 'actions/app';
import { connect } from 'react-redux';
import HelpPage from './view';
const perform = (dispatch) => ({
navigate: (path, params) => dispatch(doNavigate(path, params)),
})
const perform = dispatch => ({
navigate: (path, params) => dispatch(doNavigate(path, params))
});
export default connect(null, perform)(HelpPage)
export default connect(null, perform)(HelpPage);

View file

@ -2,8 +2,8 @@
import React from 'react';
import lbry from 'lbry.js';
import Link from 'component/link';
import SubHeader from 'component/subHeader'
import {BusyMessage} from 'component/common'
import SubHeader from 'component/subHeader';
import { BusyMessage } from 'component/common';
class HelpPage extends React.Component {
constructor(props) {
@ -18,20 +18,20 @@ class HelpPage extends React.Component {
}
componentWillMount() {
lbry.getAppVersionInfo().then(({remoteVersion, upgradeAvailable}) => {
lbry.getAppVersionInfo().then(({ remoteVersion, upgradeAvailable }) => {
this.setState({
uiVersion: remoteVersion,
upgradeAvailable: upgradeAvailable
});
});
lbry.call('version', {}, (info) => {
lbry.call('version', {}, info => {
this.setState({
versionInfo: info
})
})
lbry.getSessionInfo((info) => {
});
});
lbry.getSessionInfo(info => {
this.setState({
lbryId: info.lbry_id,
lbryId: info.lbry_id
});
});
}
@ -39,16 +39,16 @@ class HelpPage extends React.Component {
render() {
let ver, osName, platform, newVerLink;
const {
navigate
} = this.props
const { navigate } = this.props;
if (this.state.versionInfo) {
ver = this.state.versionInfo;
if (ver.os_system == 'Darwin') {
osName = (parseInt(ver.os_release.match(/^\d+/)) < 16 ? 'Mac OS X' : 'Mac OS');
osName = parseInt(ver.os_release.match(/^\d+/)) < 16
? 'Mac OS X'
: 'Mac OS';
platform = `${osName} ${ver.os_release}`
platform = `${osName} ${ver.os_release}`;
newVerLink = 'https://lbry.io/get/lbry.dmg';
} else if (ver.os_system == 'Linux') {
platform = `Linux (${ver.platform})`;
@ -66,68 +66,96 @@ class HelpPage extends React.Component {
<SubHeader />
<section className="card">
<div className="card__title-primary">
<h3>{__("Read the FAQ")}</h3>
<h3>{__('Read the FAQ')}</h3>
</div>
<div className="card__content">
<p>{__("Our FAQ answers many common questions.")}</p>
<p><Link href="https://lbry.io/faq" label={__("Read the FAQ")} icon="icon-question" button="alt"/></p>
<p>{__('Our FAQ answers many common questions.')}</p>
<p>
<Link
href="https://lbry.io/faq"
label={__('Read the FAQ')}
icon="icon-question"
button="alt"
/>
</p>
</div>
</section>
<section className="card">
<div className="card__title-primary">
<h3>{__("Get Live Help")}</h3>
<h3>{__('Get Live Help')}</h3>
</div>
<div className="card__content">
<p>
{__("Live help is available most hours in the")} <strong>#help</strong> {__("channel of our Slack chat room.")}
{__('Live help is available most hours in the')}
{' '}<strong>#help</strong>
{' '}{__('channel of our Slack chat room.')}
</p>
<p>
<Link button="alt" label={__("Join Our Slack")} icon="icon-slack" href="https://slack.lbry.io" />
<Link
button="alt"
label={__('Join Our Slack')}
icon="icon-slack"
href="https://slack.lbry.io"
/>
</p>
</div>
</section>
<section className="card">
<div className="card__title-primary"><h3>{__("Report a Bug")}</h3></div>
<div className="card__title-primary">
<h3>{__('Report a Bug')}</h3>
</div>
<div className="card__content">
<p>{__("Did you find something wrong?")}</p>
<p><Link onClick={() => navigate('report')} label={__("Submit a Bug Report")} icon="icon-bug" button="alt" /></p>
<div className="meta">{__("Thanks! LBRY is made by its users.")}</div>
<p>{__('Did you find something wrong?')}</p>
<p>
<Link
onClick={() => navigate('report')}
label={__('Submit a Bug Report')}
icon="icon-bug"
button="alt"
/>
</p>
<div className="meta">
{__('Thanks! LBRY is made by its users.')}
</div>
</div>
</section>
<section className="card">
<div className="card__title-primary"><h3>{__("About")}</h3></div>
<div className="card__title-primary"><h3>{__('About')}</h3></div>
<div className="card__content">
{ this.state.upgradeAvailable === null ? '' :
( this.state.upgradeAvailable ?
<p>{__("A newer version of LBRY is available.")} <Link href={newVerLink} label={__("Download now!")} /></p>
: <p>{__("Your copy of LBRY is up to date.")}</p>)}
{ this.state.uiVersion && ver ?
<table className="table-standard">
{this.state.upgradeAvailable === null
? ''
: this.state.upgradeAvailable
? <p>
{__('A newer version of LBRY is available.')}
{' '}<Link href={newVerLink} label={__('Download now!')} />
</p>
: <p>{__('Your copy of LBRY is up to date.')}</p>}
{this.state.uiVersion && ver
? <table className="table-standard">
<tbody>
<tr>
<th>{__("daemon (lbrynet)")}</th>
<th>{__('daemon (lbrynet)')}</th>
<td>{ver.lbrynet_version}</td>
</tr>
<tr>
<th>{__("wallet (lbryum)")}</th>
<th>{__('wallet (lbryum)')}</th>
<td>{ver.lbryum_version}</td>
</tr>
<tr>
<th>{__("interface")}</th>
<th>{__('interface')}</th>
<td>{this.state.uiVersion}</td>
</tr>
<tr>
<th>{__("Platform")}</th>
<th>{__('Platform')}</th>
<td>{platform}</td>
</tr>
<tr>
<th>{__("Installation ID")}</th>
<th>{__('Installation ID')}</th>
<td>{this.state.lbryId}</td>
</tr>
</tbody>
</table> :
<BusyMessage message={__("Looking up version info")} />
}
</table>
: <BusyMessage message={__('Looking up version info')} />}
</div>
</section>
</main>

View file

@ -1,23 +1,16 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import {
doNavigate,
doHistoryBack,
} from 'actions/app'
import {
selectMyClaims
} from 'selectors/claims'
import PublishPage from './view'
import React from 'react';
import { connect } from 'react-redux';
import { doNavigate, doHistoryBack } from 'actions/app';
import { selectMyClaims } from 'selectors/claims';
import PublishPage from './view';
const select = (state) => ({
const select = state => ({
myClaims: selectMyClaims(state)
})
});
const perform = (dispatch) => ({
const perform = dispatch => ({
back: () => dispatch(doHistoryBack()),
navigate: (path) => dispatch(doNavigate(path)),
})
navigate: path => dispatch(doNavigate(path))
});
export default connect(select, perform)(PublishPage)
export default connect(select, perform)(PublishPage);

View file

@ -1,7 +1,7 @@
import React from 'react';
import lbry from 'lbry';
import lbryuri from 'lbryuri'
import {FormField, FormRow} from 'component/form.js';
import lbryuri from 'lbryuri';
import { FormField, FormRow } from 'component/form.js';
import Link from 'component/link';
import rewards from 'rewards';
import Modal from 'component/modal';
@ -36,18 +36,18 @@ class PublishPage extends React.Component {
errorMessage: null,
submitting: false,
creatingChannel: false,
modal: null,
modal: null
};
}
_updateChannelList(channel) {
// Calls API to update displayed list of channels. If a channel name is provided, will select
// that channel at the same time (used immediately after creating a channel)
lbry.channel_list_mine().then((channels) => {
rewards.claimReward(rewards.TYPE_FIRST_CHANNEL).then(() => {}, () => {})
lbry.channel_list_mine().then(channels => {
rewards.claimReward(rewards.TYPE_FIRST_CHANNEL).then(() => {}, () => {});
this.setState({
channels: channels,
... channel ? {channel} : {}
...(channel ? { channel } : {})
});
});
}
@ -58,7 +58,7 @@ class PublishPage extends React.Component {
}
this.setState({
submitting: true,
submitting: true
});
let checkFields = this._requiredFields;
@ -84,7 +84,7 @@ class PublishPage extends React.Component {
if (missingFieldFound) {
this.setState({
submitting: false,
submitting: false
});
return;
}
@ -99,7 +99,14 @@ class PublishPage extends React.Component {
var metadata = {};
}
for (let metaField of ['title', 'description', 'thumbnail', 'license', 'license_url', 'language']) {
for (let metaField of [
'title',
'description',
'thumbnail',
'license',
'license_url',
'language'
]) {
var value = this.refs['meta_' + metaField].getValue();
if (value !== '') {
metadata[metaField] = value;
@ -118,26 +125,33 @@ class PublishPage extends React.Component {
name: this.state.name,
bid: parseFloat(this.state.bid),
metadata: metadata,
... this.state.channel != 'new' && this.state.channel != 'anonymous' ? {channel_name: this.state.channel} : {},
...(this.state.channel != 'new' && this.state.channel != 'anonymous'
? { channel_name: this.state.channel }
: {})
};
if (this.refs.file.getValue() !== '') {
publishArgs.file_path = this.refs.file.getValue();
}
lbry.publish(publishArgs, (message) => {
lbry.publish(
publishArgs,
message => {
this.handlePublishStarted();
}, null, (error) => {
},
null,
error => {
this.handlePublishError(error);
});
}
);
};
if (this.state.isFee) {
lbry.wallet_unused_address().then((address) => {
lbry.wallet_unused_address().then(address => {
metadata.fee = {};
metadata.fee[this.state.feeCurrency] = {
amount: parseFloat(this.state.feeAmount),
address: address,
address: address
};
doPublish();
@ -149,19 +163,19 @@ class PublishPage extends React.Component {
handlePublishStarted() {
this.setState({
modal: 'publishStarted',
modal: 'publishStarted'
});
}
handlePublishStartedConfirmed() {
this.props.navigate('/published')
this.props.navigate('/published');
}
handlePublishError(error) {
this.setState({
submitting: false,
modal: 'error',
errorMessage: error.message,
errorMessage: error.message
});
}
@ -172,14 +186,16 @@ class PublishPage extends React.Component {
this.setState({
rawName: '',
name: '',
nameResolved: false,
nameResolved: false
});
return;
}
if (!lbryuri.isValidName(rawName, false)) {
this.refs.name.showError(__("LBRY names must contain only letters, numbers and dashes."));
this.refs.name.showError(
__('LBRY names must contain only letters, numbers and dashes.')
);
return;
}
@ -188,32 +204,36 @@ class PublishPage extends React.Component {
rawName: rawName,
name: name,
nameResolved: null,
myClaimExists: null,
myClaimExists: null
});
const myClaimInfo = Object.values(this.props.myClaims).find(claim => claim.name === name)
const myClaimInfo = Object.values(this.props.myClaims).find(
claim => claim.name === name
);
this.setState({
myClaimExists: !!myClaimInfo,
myClaimExists: !!myClaimInfo
});
lbry.resolve({uri: name}).then((claimInfo) => {
lbry.resolve({ uri: name }).then(
claimInfo => {
if (name != this.state.name) {
return;
}
if (!claimInfo) {
this.setState({
nameResolved: false,
nameResolved: false
});
} else {
const topClaimIsMine = myClaimInfo && myClaimInfo.amount >= claimInfo.amount;
const topClaimIsMine =
myClaimInfo && myClaimInfo.amount >= claimInfo.amount;
const newState = {
nameResolved: true,
topClaimValue: parseFloat(claimInfo.amount),
myClaimExists: !!myClaimInfo,
myClaimValue: myClaimInfo ? parseFloat(myClaimInfo.amount) : null,
myClaimMetadata: myClaimInfo ? myClaimInfo.value : null,
topClaimIsMine: topClaimIsMine,
topClaimIsMine: topClaimIsMine
};
if (topClaimIsMine) {
@ -225,30 +245,33 @@ class PublishPage extends React.Component {
this.setState(newState);
}
}, () => { // Assume an error means the name is available
},
() => {
// Assume an error means the name is available
this.setState({
name: name,
nameResolved: false,
myClaimExists: false,
});
myClaimExists: false
});
}
);
}
handleBidChange(event) {
this.setState({
bid: event.target.value,
bid: event.target.value
});
}
handleFeeAmountChange(event) {
this.setState({
feeAmount: event.target.value,
feeAmount: event.target.value
});
}
handleFeeCurrencyChange(event) {
this.setState({
feeCurrency: event.target.value,
feeCurrency: event.target.value
});
}
@ -259,14 +282,16 @@ class PublishPage extends React.Component {
}
handleLicenseChange(event) {
var licenseType = event.target.options[event.target.selectedIndex].getAttribute('data-license-type');
var licenseType = event.target.options[
event.target.selectedIndex
].getAttribute('data-license-type');
var newState = {
copyrightChosen: licenseType == 'copyright',
otherLicenseChosen: licenseType == 'other',
otherLicenseChosen: licenseType == 'other'
};
if (licenseType == 'copyright') {
newState.copyrightNotice = __("All rights reserved.")
newState.copyrightNotice = __('All rights reserved.');
}
this.setState(newState);
@ -274,19 +299,19 @@ class PublishPage extends React.Component {
handleCopyrightNoticeChange(event) {
this.setState({
copyrightNotice: event.target.value,
copyrightNotice: event.target.value
});
}
handleOtherLicenseDescriptionChange(event) {
this.setState({
otherLicenseDescription: event.target.value,
otherLicenseDescription: event.target.value
});
}
handleOtherLicenseUrlChange(event) {
this.setState({
otherLicenseUrl: event.target.value,
otherLicenseUrl: event.target.value
});
}
@ -294,64 +319,83 @@ class PublishPage extends React.Component {
const channel = event.target.value;
this.setState({
channel: channel,
channel: channel
});
}
handleNewChannelNameChange(event) {
const newChannelName = (event.target.value.startsWith('@') ? event.target.value : '@' + event.target.value);
const newChannelName = event.target.value.startsWith('@')
? event.target.value
: '@' + event.target.value;
if (newChannelName.length > 1 && !lbryuri.isValidName(newChannelName.substr(1), false)) {
this.refs.newChannelName.showError(__("LBRY channel names must contain only letters, numbers and dashes."));
if (
newChannelName.length > 1 &&
!lbryuri.isValidName(newChannelName.substr(1), false)
) {
this.refs.newChannelName.showError(
__('LBRY channel names must contain only letters, numbers and dashes.')
);
return;
} else {
this.refs.newChannelName.clearError()
this.refs.newChannelName.clearError();
}
this.setState({
newChannelName: newChannelName,
newChannelName: newChannelName
});
}
handleNewChannelBidChange(event) {
this.setState({
newChannelBid: event.target.value,
newChannelBid: event.target.value
});
}
handleTOSChange(event) {
this.setState({
TOSAgreed: event.target.checked,
TOSAgreed: event.target.checked
});
}
handleCreateChannelClick(event) {
if (this.state.newChannelName.length < 5) {
this.refs.newChannelName.showError(__("LBRY channel names must be at least 4 characters in length."));
this.refs.newChannelName.showError(
__('LBRY channel names must be at least 4 characters in length.')
);
return;
}
this.setState({
creatingChannel: true,
creatingChannel: true
});
const newChannelName = this.state.newChannelName;
lbry.channel_new({channel_name: newChannelName, amount: parseInt(this.state.newChannelBid)}).then(() => {
lbry
.channel_new({
channel_name: newChannelName,
amount: parseInt(this.state.newChannelBid)
})
.then(
() => {
setTimeout(() => {
this.setState({
creatingChannel: false,
creatingChannel: false
});
this._updateChannelList(newChannelName);
}, 5000);
}, (error) => {
},
error => {
// TODO: better error handling
this.refs.newChannelName.showError(__("Unable to create channel due to an internal error."));
this.refs.newChannelName.showError(
__('Unable to create channel due to an internal error.')
);
this.setState({
creatingChannel: false,
});
creatingChannel: false
});
}
);
}
getLicenseUrl() {
if (!this.refs.meta_license) {
@ -359,7 +403,10 @@ class PublishPage extends React.Component {
} else if (this.state.otherLicenseChosen) {
return this.state.otherLicenseUrl;
} else {
return this.refs.meta_license.getSelectedElement().getAttribute('data-url') || '' ;
return (
this.refs.meta_license.getSelectedElement().getAttribute('data-url') ||
''
);
}
}
@ -369,25 +416,33 @@ class PublishPage extends React.Component {
onFileChange() {
if (this.refs.file.getValue()) {
this.setState({ hasFile: true })
this.setState({ hasFile: true });
} else {
this.setState({ hasFile: false })
this.setState({ hasFile: false });
}
}
getNameBidHelpText() {
if (!this.state.name) {
return __("Select a URL for this publish.");
return __('Select a URL for this publish.');
} else if (this.state.nameResolved === false) {
return __("This URL is unused.");
return __('This URL is unused.');
} else if (this.state.myClaimExists) {
return __("You have already used this URL. Publishing to it again will update your previous publish.")
return __(
'You have already used this URL. Publishing to it again will update your previous publish.'
);
} else if (this.state.topClaimValue) {
return <span>{__n("A deposit of at least \"%s\" credit is required to win \"%s\". However, you can still get a permanent URL for any amount."
, "A deposit of at least \"%s\" credits is required to win \"%s\". However, you can still get a permanent URL for any amount."
, this.state.topClaimValue /*pluralization param*/
, this.state.topClaimValue, this.state.name /*regular params*/
)}</span>
return (
<span>
{__n(
'A deposit of at least "%s" credit is required to win "%s". However, you can still get a permanent URL for any amount.',
'A deposit of at least "%s" credits is required to win "%s". However, you can still get a permanent URL for any amount.',
this.state.topClaimValue /*pluralization param*/,
this.state.topClaimValue,
this.state.name /*regular params*/
)}
</span>
);
} else {
return '';
}
@ -395,7 +450,7 @@ class PublishPage extends React.Component {
closeModal() {
this.setState({
modal: null,
modal: null
});
}
@ -404,49 +459,100 @@ class PublishPage extends React.Component {
return null;
}
const lbcInputHelp = __("This LBC remains yours and the deposit can be undone at any time.");
const lbcInputHelp = __(
'This LBC remains yours and the deposit can be undone at any time.'
);
return (
<main className="main--single-column">
<form onSubmit={(event) => { this.handleSubmit(event) }}>
<form
onSubmit={event => {
this.handleSubmit(event);
}}
>
<section className="card">
<div className="card__title-primary">
<h4>{__("Content")}</h4>
<h4>{__('Content')}</h4>
<div className="card__subtitle">
{__("What are you publishing?")}
{__('What are you publishing?')}
</div>
</div>
<div className="card__content">
<FormRow name="file" label="File" ref="file" type="file" onChange={(event) => { this.onFileChange(event) }}
helper={this.state.myClaimExists ? __("If you don't choose a file, the file from your existing claim will be used.") : null}/>
<FormRow
name="file"
label="File"
ref="file"
type="file"
onChange={event => {
this.onFileChange(event);
}}
helper={
this.state.myClaimExists
? __(
"If you don't choose a file, the file from your existing claim will be used."
)
: null
}
/>
</div>
{ !this.state.hasFile ? '' :
<div>
{!this.state.hasFile
? ''
: <div>
<div className="card__content">
<FormRow label={__("Title")} type="text" ref="meta_title" name="title" placeholder={__("Title")} />
<FormRow
label={__('Title')}
type="text"
ref="meta_title"
name="title"
placeholder={__('Title')}
/>
</div>
<div className="card__content">
<FormRow type="text" label={__("Thumbnail URL")} ref="meta_thumbnail" name="thumbnail" placeholder="http://spee.ch/mylogo" />
<FormRow
type="text"
label={__('Thumbnail URL')}
ref="meta_thumbnail"
name="thumbnail"
placeholder="http://spee.ch/mylogo"
/>
</div>
<div className="card__content">
<FormRow label={__("Description")} type="textarea" ref="meta_description" name="description" placeholder={__("Description of your content")} />
<FormRow
label={__('Description')}
type="textarea"
ref="meta_description"
name="description"
placeholder={__('Description of your content')}
/>
</div>
<div className="card__content">
<FormRow label={__("Language")} type="select" defaultValue="en" ref="meta_language" name="language">
<option value="en">{__("English")}</option>
<option value="zh">{__("Chinese")}</option>
<option value="fr">{__("French")}</option>
<option value="de">{__("German")}</option>
<option value="jp">{__("Japanese")}</option>
<option value="ru">{__("Russian")}</option>
<option value="es">{__("Spanish")}</option>
<FormRow
label={__('Language')}
type="select"
defaultValue="en"
ref="meta_language"
name="language"
>
<option value="en">{__('English')}</option>
<option value="zh">{__('Chinese')}</option>
<option value="fr">{__('French')}</option>
<option value="de">{__('German')}</option>
<option value="jp">{__('Japanese')}</option>
<option value="ru">{__('Russian')}</option>
<option value="es">{__('Spanish')}</option>
</FormRow>
</div>
<div className="card__content">
<FormRow type="select" label={__("Maturity")} defaultValue="en" ref="meta_nsfw" name="nsfw">
<FormRow
type="select"
label={__('Maturity')}
defaultValue="en"
ref="meta_nsfw"
name="nsfw"
>
{/* <option value=""></option> */}
<option value="0">{__("All Ages")}</option>
<option value="1">{__("Adults Only")}</option>
<option value="0">{__('All Ages')}</option>
<option value="1">{__('Adults Only')}</option>
</FormRow>
</div>
</div>}
@ -454,136 +560,345 @@ class PublishPage extends React.Component {
<section className="card">
<div className="card__title-primary">
<h4>{__("Access")}</h4>
<h4>{__('Access')}</h4>
<div className="card__subtitle">
{__("How much does this content cost?")}
{__('How much does this content cost?')}
</div>
</div>
<div className="card__content">
<div className="form-row__label-row">
<label className="form-row__label">{__("Price")}</label>
<label className="form-row__label">{__('Price')}</label>
</div>
<FormRow label={__("Free")} type="radio" name="isFree" value="1" onChange={ () => { this.handleFeePrefChange(false) } } defaultChecked={!this.state.isFee} />
<FormField type="radio" name="isFree" label={!this.state.isFee ? __('Choose price...') : __('Price ') }
onChange={ () => { this.handleFeePrefChange(true) } } defaultChecked={this.state.isFee} />
<FormRow
label={__('Free')}
type="radio"
name="isFree"
value="1"
onChange={() => {
this.handleFeePrefChange(false);
}}
defaultChecked={!this.state.isFee}
/>
<FormField
type="radio"
name="isFree"
label={!this.state.isFee ? __('Choose price...') : __('Price ')}
onChange={() => {
this.handleFeePrefChange(true);
}}
defaultChecked={this.state.isFee}
/>
<span className={!this.state.isFee ? 'hidden' : ''}>
<FormField type="number" className="form-field__input--inline" step="0.01" placeholder="1.00" onChange={(event) => this.handleFeeAmountChange(event)} /> <FormField type="select" onChange={(event) => { this.handleFeeCurrencyChange(event) }}>
<option value="USD">{__("US Dollars")}</option>
<option value="LBC">{__("LBRY credits")}</option>
<FormField
type="number"
className="form-field__input--inline"
step="0.01"
placeholder="1.00"
onChange={event => this.handleFeeAmountChange(event)}
/>
{' '}
<FormField
type="select"
onChange={event => {
this.handleFeeCurrencyChange(event);
}}
>
<option value="USD">{__('US Dollars')}</option>
<option value="LBC">{__('LBRY credits')}</option>
</FormField>
</span>
{ this.state.isFee ?
<div className="form-field__helper">
{__("If you choose to price this content in dollars, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase.")}
</div> : '' }
<FormRow label="License" type="select" ref="meta_license" name="license" onChange={(event) => { this.handleLicenseChange(event) }}>
<option></option>
<option>{__("Public Domain")}</option>
<option data-url="https://creativecommons.org/licenses/by/4.0/legalcode">{__("Creative Commons Attribution 4.0 International")}</option>
<option data-url="https://creativecommons.org/licenses/by-sa/4.0/legalcode">{__("Creative Commons Attribution-ShareAlike 4.0 International")}</option>
<option data-url="https://creativecommons.org/licenses/by-nd/4.0/legalcode">{__("Creative Commons Attribution-NoDerivatives 4.0 International")}</option>
<option data-url="https://creativecommons.org/licenses/by-nc/4.0/legalcode">{__("Creative Commons Attribution-NonCommercial 4.0 International")}</option>
<option data-url="https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode">{__("Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International")}</option>
<option data-url="https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode">{__("Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International")}</option>
<option data-license-type="copyright" {... this.state.copyrightChosen ? {value: this.state.copyrightNotice} : {}}>{__("Copyrighted...")}</option>
<option data-license-type="other" {... this.state.otherLicenseChosen ? {value: this.state.otherLicenseDescription} : {}}>{__("Other...")}</option>
{this.state.isFee
? <div className="form-field__helper">
{__(
'If you choose to price this content in dollars, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase.'
)}
</div>
: ''}
<FormRow
label="License"
type="select"
ref="meta_license"
name="license"
onChange={event => {
this.handleLicenseChange(event);
}}
>
<option />
<option>{__('Public Domain')}</option>
<option data-url="https://creativecommons.org/licenses/by/4.0/legalcode">
{__('Creative Commons Attribution 4.0 International')}
</option>
<option data-url="https://creativecommons.org/licenses/by-sa/4.0/legalcode">
{__(
'Creative Commons Attribution-ShareAlike 4.0 International'
)}
</option>
<option data-url="https://creativecommons.org/licenses/by-nd/4.0/legalcode">
{__(
'Creative Commons Attribution-NoDerivatives 4.0 International'
)}
</option>
<option data-url="https://creativecommons.org/licenses/by-nc/4.0/legalcode">
{__(
'Creative Commons Attribution-NonCommercial 4.0 International'
)}
</option>
<option data-url="https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode">
{__(
'Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International'
)}
</option>
<option data-url="https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode">
{__(
'Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International'
)}
</option>
<option
data-license-type="copyright"
{...(this.state.copyrightChosen
? { value: this.state.copyrightNotice }
: {})}
>
{__('Copyrighted...')}
</option>
<option
data-license-type="other"
{...(this.state.otherLicenseChosen
? { value: this.state.otherLicenseDescription }
: {})}
>
{__('Other...')}
</option>
</FormRow>
<FormField type="hidden" ref="meta_license_url" name="license_url" value={this.getLicenseUrl()} />
<FormField
type="hidden"
ref="meta_license_url"
name="license_url"
value={this.getLicenseUrl()}
/>
{this.state.copyrightChosen
? <FormRow label={__("Copyright notice")} type="text" name="copyright-notice"
value={this.state.copyrightNotice} onChange={(event) => { this.handleCopyrightNoticeChange(event) }} />
? <FormRow
label={__('Copyright notice')}
type="text"
name="copyright-notice"
value={this.state.copyrightNotice}
onChange={event => {
this.handleCopyrightNoticeChange(event);
}}
/>
: null}
{this.state.otherLicenseChosen ?
<FormRow label={__("License description")} type="text" name="other-license-description" onChange={(event) => { this.handleOtherLicenseDescriptionChange() }} />
{this.state.otherLicenseChosen
? <FormRow
label={__('License description')}
type="text"
name="other-license-description"
onChange={event => {
this.handleOtherLicenseDescriptionChange();
}}
/>
: null}
{this.state.otherLicenseChosen ?
<FormRow label={__("License URL")} type="text" name="other-license-url" onChange={(event) => { this.handleOtherLicenseUrlChange(event) }} />
{this.state.otherLicenseChosen
? <FormRow
label={__('License URL')}
type="text"
name="other-license-url"
onChange={event => {
this.handleOtherLicenseUrlChange(event);
}}
/>
: null}
</div>
</section>
<section className="card">
<div className="card__title-primary">
<h4>{__("Identity")}</h4>
<h4>{__('Identity')}</h4>
<div className="card__subtitle">
{__("Who created this content?")}
{__('Who created this content?')}
</div>
</div>
<div className="card__content">
<FormRow type="select" tabIndex="1" onChange={(event) => { this.handleChannelChange(event) }} value={this.state.channel}>
<option key="anonymous" value="anonymous">{__("Anonymous")}</option>
{this.state.channels.map(({name}) => <option key={name} value={name}>{name}</option>)}
<option key="new" value="new">{__("New identity...")}</option>
<FormRow
type="select"
tabIndex="1"
onChange={event => {
this.handleChannelChange(event);
}}
value={this.state.channel}
>
<option key="anonymous" value="anonymous">
{__('Anonymous')}
</option>
{this.state.channels.map(({ name }) =>
<option key={name} value={name}>{name}</option>
)}
<option key="new" value="new">{__('New identity...')}</option>
</FormRow>
</div>
{this.state.channel == 'new' ?
<div className="card__content">
<FormRow label={__("Name")} type="text" onChange={(event) => { this.handleNewChannelNameChange(event) }} ref={newChannelName => { this.refs.newChannelName = newChannelName }}
value={this.state.newChannelName} />
<FormRow label={__("Deposit")}
{this.state.channel == 'new'
? <div className="card__content">
<FormRow
label={__('Name')}
type="text"
onChange={event => {
this.handleNewChannelNameChange(event);
}}
ref={newChannelName => {
this.refs.newChannelName = newChannelName;
}}
value={this.state.newChannelName}
/>
<FormRow
label={__('Deposit')}
postfix="LBC"
step="0.01"
type="number"
helper={lbcInputHelp}
onChange={(event) => { this.handleNewChannelBidChange(event) }}
value={this.state.newChannelBid} />
onChange={event => {
this.handleNewChannelBidChange(event);
}}
value={this.state.newChannelBid}
/>
<div className="form-row-submit">
<Link button="primary" label={!this.state.creatingChannel ? __("Create identity") : __("Creating identity...")} onClick={(event) => { this.handleCreateChannelClick(event) }} disabled={this.state.creatingChannel} />
<Link
button="primary"
label={
!this.state.creatingChannel
? __('Create identity')
: __('Creating identity...')
}
onClick={event => {
this.handleCreateChannelClick(event);
}}
disabled={this.state.creatingChannel}
/>
</div>
</div>
: null}
</section>
<section className="card">
<div className="card__title-primary">
<h4>{__("Address")}</h4>
<div className="card__subtitle">{__("Where should this content permanently reside?")} <Link label={__("Read more")} href="https://lbry.io/faq/naming" />.</div>
<h4>{__('Address')}</h4>
<div className="card__subtitle">
{__('Where should this content permanently reside?')}
{' '}<Link
label={__('Read more')}
href="https://lbry.io/faq/naming"
/>.
</div>
</div>
<div className="card__content">
<FormRow prefix="lbry://" type="text" ref="name" placeholder="myname" value={this.state.rawName} onChange={(event) => { this.handleNameChange(event) }}
helper={this.getNameBidHelpText()} />
<FormRow
prefix="lbry://"
type="text"
ref="name"
placeholder="myname"
value={this.state.rawName}
onChange={event => {
this.handleNameChange(event);
}}
helper={this.getNameBidHelpText()}
/>
</div>
{ this.state.rawName ?
<div className="card__content">
<FormRow ref="bid"
{this.state.rawName
? <div className="card__content">
<FormRow
ref="bid"
type="number"
step="0.01"
label={__("Deposit")}
label={__('Deposit')}
postfix="LBC"
onChange={(event) => { this.handleBidChange(event) }}
onChange={event => {
this.handleBidChange(event);
}}
value={this.state.bid}
placeholder={this.state.nameResolved ? this.state.topClaimValue + 10 : 100}
helper={lbcInputHelp} />
</div> : '' }
placeholder={
this.state.nameResolved
? this.state.topClaimValue + 10
: 100
}
helper={lbcInputHelp}
/>
</div>
: ''}
</section>
<section className="card">
<div className="card__title-primary">
<h4>{__("Terms of Service")}</h4>
<h4>{__('Terms of Service')}</h4>
</div>
<div className="card__content">
<FormRow label={
<span>{__("I agree to the")} <Link href="https://www.lbry.io/termsofservice" label={__("LBRY terms of service")} checked={this.state.TOSAgreed} /></span>
} type="checkbox" name="tos_agree" ref={(field) => { this.refs.tos_agree = field }} onChange={(event) => { this.handleTOSChange(event)}} />
<FormRow
label={
<span>
{__('I agree to the')}
{' '}<Link
href="https://www.lbry.io/termsofservice"
label={__('LBRY terms of service')}
checked={this.state.TOSAgreed}
/>
</span>
}
type="checkbox"
name="tos_agree"
ref={field => {
this.refs.tos_agree = field;
}}
onChange={event => {
this.handleTOSChange(event);
}}
/>
</div>
</section>
<div className="card-series-submit">
<Link button="primary" label={!this.state.submitting ? __("Publish") : __("Publishing...")} onClick={(event) => { this.handleSubmit(event) }} disabled={this.state.submitting} />
<Link button="cancel" onClick={this.props.back} label={__("Cancel")} />
<Link
button="primary"
label={
!this.state.submitting ? __('Publish') : __('Publishing...')
}
onClick={event => {
this.handleSubmit(event);
}}
disabled={this.state.submitting}
/>
<Link
button="cancel"
onClick={this.props.back}
label={__('Cancel')}
/>
<input type="submit" className="hidden" />
</div>
</form>
<Modal isOpen={this.state.modal == 'publishStarted'} contentLabel={__("File published")}
onConfirmed={(event) => { this.handlePublishStartedConfirmed(event) }}>
<p>{__("Your file has been published to LBRY at the address")} <code>lbry://{this.state.name}</code>!</p>
<p>{__('The file will take a few minutes to appear for other LBRY users. Until then it will be listed as "pending" under your published files.')}</p>
<Modal
isOpen={this.state.modal == 'publishStarted'}
contentLabel={__('File published')}
onConfirmed={event => {
this.handlePublishStartedConfirmed(event);
}}
>
<p>
{__('Your file has been published to LBRY at the address')}
{' '}<code>lbry://{this.state.name}</code>!
</p>
<p>
{__(
'The file will take a few minutes to appear for other LBRY users. Until then it will be listed as "pending" under your published files.'
)}
</p>
</Modal>
<Modal isOpen={this.state.modal == 'error'} contentLabel={__("Error publishing file")}
onConfirmed={(event) => { this.closeModal(event) }}>
{__("The following error occurred when attempting to publish your file")}: {this.state.errorMessage}
<Modal
isOpen={this.state.modal == 'error'}
contentLabel={__('Error publishing file')}
onConfirmed={event => {
this.closeModal(event);
}}
>
{__(
'The following error occurred when attempting to publish your file'
)}: {this.state.errorMessage}
</Modal>
</main>
);

View file

@ -1,6 +1,6 @@
import React from 'react';
import Link from 'component/link';
import {FormRow} from 'component/form'
import { FormRow } from 'component/form';
import Modal from '../component/modal.js';
import lbry from '../lbry.js';
@ -10,7 +10,7 @@ class ReportPage extends React.Component {
this.state = {
submitting: false,
modal: null,
modal: null
};
}
@ -22,7 +22,7 @@ class ReportPage extends React.Component {
lbry.reportBug(this._messageArea.value, () => {
this.setState({
submitting: false,
modal: 'submitted',
modal: 'submitted'
});
});
this._messageArea.value = '';
@ -31,8 +31,8 @@ class ReportPage extends React.Component {
closeModal() {
this.setState({
modal: null,
})
modal: null
});
}
render() {
@ -40,25 +40,58 @@ class ReportPage extends React.Component {
<main className="main--single-column">
<section className="card">
<div className="card__content">
<h3>{__("Report an Issue")}</h3>
<p>{__("Please describe the problem you experienced and any information you think might be useful to us. Links to screenshots are great!")}</p>
<h3>{__('Report an Issue')}</h3>
<p>
{__(
'Please describe the problem you experienced and any information you think might be useful to us. Links to screenshots are great!'
)}
</p>
<div className="form-row">
<FormRow type="textarea" ref={(t) => this._messageArea = t} rows="10" name="message" placeholder={__("Description of your issue")} />
<FormRow
type="textarea"
ref={t => (this._messageArea = t)}
rows="10"
name="message"
placeholder={__('Description of your issue')}
/>
</div>
<div className="form-row form-row-submit">
<button onClick={(event) => { this.submitMessage(event) }} className={'button-block button-primary ' + (this.state.submitting ? 'disabled' : '')}>{this.state.submitting ? __('Submitting...') : __('Submit Report')}</button>
<button
onClick={event => {
this.submitMessage(event);
}}
className={
'button-block button-primary ' +
(this.state.submitting ? 'disabled' : '')
}
>
{this.state.submitting
? __('Submitting...')
: __('Submit Report')}
</button>
</div>
</div>
</section>
<section className="card">
<div className="card__content">
<h3>{__("Developer?")}</h3>
{__("You can also")} <Link href="https://github.com/lbryio/lbry/issues" label={__("submit an issue on GitHub")}/>.
<h3>{__('Developer?')}</h3>
{__('You can also')}
{' '}<Link
href="https://github.com/lbryio/lbry/issues"
label={__('submit an issue on GitHub')}
/>.
</div>
</section>
<Modal isOpen={this.state.modal == 'submitted'} contentLabel={__("Bug report submitted")}
onConfirmed={(event) => { this.closeModal(event) }}>
{__("Your bug report has been submitted! Thank you for your feedback.")}
<Modal
isOpen={this.state.modal == 'submitted'}
contentLabel={__('Bug report submitted')}
onConfirmed={event => {
this.closeModal(event);
}}
>
{__(
'Your bug report has been submitted! Thank you for your feedback.'
)}
</Modal>
</main>
);

View file

@ -1,8 +1,8 @@
import React from 'react';
import lbryio from 'lbryio';
import {CreditAmount, Icon} from 'component/common.js';
import SubHeader from 'component/subHeader'
import {RewardLink} from 'component/reward-link';
import { CreditAmount, Icon } from 'component/common.js';
import SubHeader from 'component/subHeader';
import { RewardLink } from 'component/reward-link';
export class RewardTile extends React.Component {
static propTypes = {
@ -12,7 +12,7 @@ export class RewardTile extends React.Component {
claimed: React.PropTypes.bool.isRequired,
value: React.PropTypes.number.isRequired,
onRewardClaim: React.PropTypes.func
}
};
render() {
return (
@ -24,7 +24,7 @@ export class RewardTile extends React.Component {
</div>
<div className="card__actions">
{this.props.claimed
? <span><Icon icon="icon-check" /> {__("Reward claimed.")}</span>
? <span><Icon icon="icon-check" /> {__('Reward claimed.')}</span>
: <RewardLink {...this.props} />}
</div>
<div className="card__content">{this.props.description}</div>
@ -40,22 +40,25 @@ export class RewardsPage extends React.Component {
this.state = {
userRewards: null,
failed: null,
failed: null
};
}
componentWillMount() {
this.loadRewards()
this.loadRewards();
}
loadRewards() {
lbryio.call('reward', 'list', {}).then((userRewards) => {
lbryio.call('reward', 'list', {}).then(
userRewards => {
this.setState({
userRewards: userRewards,
});
}, () => {
this.setState({failed: true })
userRewards: userRewards
});
},
() => {
this.setState({ failed: true });
}
);
}
render() {
@ -64,10 +67,30 @@ export class RewardsPage extends React.Component {
<SubHeader />
<div>
{!this.state.userRewards
? (this.state.failed ? <div className="empty">{__("Failed to load rewards.")}</div> : '')
: this.state.userRewards.map(({reward_type, reward_title, reward_description, transaction_id, reward_amount}) => {
return <RewardTile key={reward_type} onRewardClaim={this.loadRewards} type={reward_type} title={__(reward_title)} description={__(reward_description)} claimed={!!transaction_id} value={reward_amount} />;
})}
? this.state.failed
? <div className="empty">{__('Failed to load rewards.')}</div>
: ''
: this.state.userRewards.map(
({
reward_type,
reward_title,
reward_description,
transaction_id,
reward_amount
}) => {
return (
<RewardTile
key={reward_type}
onRewardClaim={this.loadRewards}
type={reward_type}
title={__(reward_title)}
description={__(reward_description)}
claimed={!!transaction_id}
value={reward_amount}
/>
);
}
)}
</div>
</main>
);

View file

@ -1,24 +1,20 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import React from 'react';
import { connect } from 'react-redux';
import {
selectIsSearching,
selectSearchQuery,
selectCurrentSearchResults,
} from 'selectors/search'
import {
doNavigate,
} from 'actions/app'
import SearchPage from './view'
selectCurrentSearchResults
} from 'selectors/search';
import { doNavigate } from 'actions/app';
import SearchPage from './view';
const select = (state) => ({
const select = state => ({
isSearching: selectIsSearching(state),
query: selectSearchQuery(state)
})
});
const perform = (dispatch) => ({
navigate: (path) => dispatch(doNavigate(path)),
})
const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path))
});
export default connect(select, perform)(SearchPage)
export default connect(select, perform)(SearchPage);

View file

@ -1,36 +1,47 @@
import React from 'react';
import lbryuri from 'lbryuri';
import FileTile from 'component/fileTile'
import FileListSearch from 'component/fileListSearch'
import {ToolTip} from 'component/tooltip.js';
import {BusyMessage} from 'component/common.js';
import FileTile from 'component/fileTile';
import FileListSearch from 'component/fileListSearch';
import { ToolTip } from 'component/tooltip.js';
import { BusyMessage } from 'component/common.js';
class SearchPage extends React.Component{
class SearchPage extends React.Component {
render() {
const {
query,
} = this.props
const { query } = this.props;
return (
<main className="main--single-column">
{ lbryuri.isValid(query) ?
<section className="section-spaced">
{lbryuri.isValid(query)
? <section className="section-spaced">
<h3 className="card-row__header">
{__("Exact URL")} <ToolTip label="?" body={__("This is the resolution of a LBRY URL and not controlled by LBRY Inc.")}
className="tooltip--header" />
{__('Exact URL')}
{' '}<ToolTip
label="?"
body={__(
'This is the resolution of a LBRY URL and not controlled by LBRY Inc.'
)}
className="tooltip--header"
/>
</h3>
<FileTile uri={lbryuri.normalize(query)} showEmpty={FileTile.SHOW_EMPTY_PUBLISH} />
</section> : '' }
<FileTile
uri={lbryuri.normalize(query)}
showEmpty={FileTile.SHOW_EMPTY_PUBLISH}
/>
</section>
: ''}
<section className="section-spaced">
<h3 className="card-row__header">
{__("Search Results for")} {query} <ToolTip label="?" body={__("These search results are provided by LBRY, Inc.")}
className="tooltip--header" />
{__('Search Results for')} {query}
{' '}<ToolTip
label="?"
body={__('These search results are provided by LBRY, Inc.')}
className="tooltip--header"
/>
</h3>
<FileListSearch query={query} />
</section>
</main>
)
);
}
}
export default SearchPage;

View file

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

View file

@ -1,31 +1,30 @@
import React from 'react';
import {FormField, FormRow} from 'component/form.js';
import SubHeader from 'component/subHeader'
import { FormField, FormRow } from 'component/form.js';
import SubHeader from 'component/subHeader';
import lbry from 'lbry.js';
class SettingsPage extends React.Component {
constructor(props) {
super(props);
const daemonSettings = this.props.daemonSettings
const daemonSettings = this.props.daemonSettings;
this.state = {
isMaxUpload: daemonSettings && daemonSettings.max_upload != 0,
isMaxDownload: daemonSettings && daemonSettings.max_download != 0,
showNsfw: lbry.getClientSetting('showNsfw'),
showUnavailable: lbry.getClientSetting('showUnavailable'),
language: lbry.getClientSetting('language'),
}
language: lbry.getClientSetting('language')
};
}
setDaemonSetting(name, value) {
this.props.setDaemonSetting(name, value)
this.props.setDaemonSetting(name, value);
}
setClientSetting(name, value) {
lbry.setClientSetting(name, value)
this._onSettingSaveSuccess()
lbry.setClientSetting(name, value);
this._onSettingSaveSuccess();
}
onRunOnStartChange(event) {
@ -76,19 +75,19 @@ class SettingsPage extends React.Component {
// this.setState({language: language})
// }
onShowUnavailableChange(event) {
}
onShowUnavailableChange(event) {}
render() {
const {
daemonSettings
} = this.props
const { daemonSettings } = this.props;
if (!daemonSettings) {
return <main className="main--single-column"><span className="empty">{__("Failed to load settings.")}</span></main>;
return (
<main className="main--single-column">
<span className="empty">{__('Failed to load settings.')}</span>
</main>
);
}
/*
/*
<section className="card">
<div className="card__content">
<h3>Run on Startup</h3>
@ -106,35 +105,50 @@ class SettingsPage extends React.Component {
<SubHeader />
<section className="card">
<div className="card__content">
<h3>{__("Download Directory")}</h3>
<h3>{__('Download Directory')}</h3>
</div>
<div className="card__content">
<FormRow type="directory"
<FormRow
type="directory"
name="download_directory"
defaultValue={daemonSettings.download_directory}
helper={__("LBRY downloads will be saved here.")}
onChange={this.onDownloadDirChange.bind(this)} />
helper={__('LBRY downloads will be saved here.')}
onChange={this.onDownloadDirChange.bind(this)}
/>
</div>
</section>
<section className="card">
<div className="card__content">
<h3>{__("Bandwidth Limits")}</h3>
<h3>{__('Bandwidth Limits')}</h3>
</div>
<div className="card__content">
<div className="form-row__label-row"><div className="form-field__label">{__("Max Upload")}</div></div>
<FormRow type="radio"
<div className="form-row__label-row">
<div className="form-field__label">{__('Max Upload')}</div>
</div>
<FormRow
type="radio"
name="max_upload_pref"
onChange={() => { this.onMaxUploadPrefChange(false) }}
onChange={() => {
this.onMaxUploadPrefChange(false);
}}
defaultChecked={!this.state.isMaxUpload}
label={__("Unlimited")} />
label={__('Unlimited')}
/>
<div className="form-row">
<FormField type="radio"
<FormField
type="radio"
name="max_upload_pref"
onChange={() => { this.onMaxUploadPrefChange(true) }}
onChange={() => {
this.onMaxUploadPrefChange(true);
}}
defaultChecked={this.state.isMaxUpload}
label={ this.state.isMaxUpload ? __("Up to") : __("Choose limit...") } />
{ this.state.isMaxUpload ?
<FormField type="number"
label={
this.state.isMaxUpload ? __('Up to') : __('Choose limit...')
}
/>
{this.state.isMaxUpload
? <FormField
type="number"
min="0"
step=".5"
defaultValue={daemonSettings.max_upload}
@ -142,27 +156,40 @@ class SettingsPage extends React.Component {
className="form-field__input--inline"
onChange={this.onMaxUploadFieldChange.bind(this)}
/>
: ''
}
{ this.state.isMaxUpload ? <span className="form-field__label">MB/s</span> : '' }
: ''}
{this.state.isMaxUpload
? <span className="form-field__label">MB/s</span>
: ''}
</div>
</div>
<div className="card__content">
<div className="form-row__label-row"><div className="form-field__label">{__("Max Download")}</div></div>
<FormRow label={__("Unlimited")}
<div className="form-row__label-row">
<div className="form-field__label">{__('Max Download')}</div>
</div>
<FormRow
label={__('Unlimited')}
type="radio"
name="max_download_pref"
onChange={() => { this.onMaxDownloadPrefChange(false) }}
defaultChecked={!this.state.isMaxDownload} />
onChange={() => {
this.onMaxDownloadPrefChange(false);
}}
defaultChecked={!this.state.isMaxDownload}
/>
<div className="form-row">
<FormField type="radio"
<FormField
type="radio"
name="max_download_pref"
onChange={() => { this.onMaxDownloadPrefChange(true) }}
onChange={() => {
this.onMaxDownloadPrefChange(true);
}}
defaultChecked={this.state.isMaxDownload}
label={ this.state.isMaxDownload ? __("Up to") : __("Choose limit...") } />
{ this.state.isMaxDownload ?
<FormField type="number"
label={
this.state.isMaxDownload ? __('Up to') : __('Choose limit...')
}
/>
{this.state.isMaxDownload
? <FormField
type="number"
min="0"
step=".5"
defaultValue={daemonSettings.max_download}
@ -170,27 +197,35 @@ class SettingsPage extends React.Component {
className="form-field__input--inline"
onChange={this.onMaxDownloadFieldChange.bind(this)}
/>
: ''
}
{ this.state.isMaxDownload ? <span className="form-field__label">MB/s</span> : '' }
: ''}
{this.state.isMaxDownload
? <span className="form-field__label">MB/s</span>
: ''}
</div>
</div>
</section>
<section className="card">
<div className="card__content">
<h3>{__("Content")}</h3>
<h3>{__('Content')}</h3>
</div>
<div className="card__content">
<FormRow type="checkbox"
<FormRow
type="checkbox"
onChange={this.onShowUnavailableChange.bind(this)}
defaultChecked={this.state.showUnavailable}
label={__("Show unavailable content in search results")} />
label={__('Show unavailable content in search results')}
/>
</div>
<div className="card__content">
<FormRow label={__("Show NSFW content")} type="checkbox"
onChange={this.onShowNsfwChange.bind(this)} defaultChecked={this.state.showNsfw}
helper={__("NSFW content may include nudity, intense sexuality, profanity, or other adult content. By displaying NSFW content, you are affirming you are of legal age to view mature content in your country or jurisdiction. ")} />
<FormRow
label={__('Show NSFW content')}
type="checkbox"
onChange={this.onShowNsfwChange.bind(this)}
defaultChecked={this.state.showNsfw}
helper={__(
'NSFW content may include nudity, intense sexuality, profanity, or other adult content. By displaying NSFW content, you are affirming you are of legal age to view mature content in your country or jurisdiction. '
)}
/>
</div>
</section>
@ -219,13 +254,17 @@ class SettingsPage extends React.Component {
<section className="card">
<div className="card__content">
<h3>{__("Share Diagnostic Data")}</h3>
<h3>{__('Share Diagnostic Data')}</h3>
</div>
<div className="card__content">
<FormRow type="checkbox"
<FormRow
type="checkbox"
onChange={this.onShareDataChange.bind(this)}
defaultChecked={daemonSettings.share_usage_data}
label={__("Help make LBRY better by contributing diagnostic data about my usage")} />
label={__(
'Help make LBRY better by contributing diagnostic data about my usage'
)}
/>
</div>
</section>
</main>

View file

@ -1,17 +1,9 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
doResolveUri,
} from 'actions/content'
import {
makeSelectClaimForUri,
} from 'selectors/claims'
import {
makeSelectIsResolvingForUri,
} from 'selectors/content'
import ShowPage from './view'
import React from 'react';
import { connect } from 'react-redux';
import { doResolveUri } from 'actions/content';
import { makeSelectClaimForUri } from 'selectors/claims';
import { makeSelectIsResolvingForUri } from 'selectors/content';
import ShowPage from './view';
const makeSelect = () => {
const selectClaim = makeSelectClaimForUri(),
@ -20,13 +12,13 @@ const makeSelect = () => {
const select = (state, props) => ({
claim: selectClaim(state, props),
isResolvingUri: selectIsResolving(state, props)
})
});
return select
}
return select;
};
const perform = (dispatch) => ({
resolveUri: (uri) => dispatch(doResolveUri(uri))
})
const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri))
});
export default connect(makeSelect, perform)(ShowPage)
export default connect(makeSelect, perform)(ShowPage);

View file

@ -1,64 +1,57 @@
import React from 'react';
import lbryuri from 'lbryuri'
import {
BusyMessage,
} from 'component/common';
import ChannelPage from 'page/channel'
import FilePage from 'page/filePage'
import lbryuri from 'lbryuri';
import { BusyMessage } from 'component/common';
import ChannelPage from 'page/channel';
import FilePage from 'page/filePage';
class ShowPage extends React.Component{
class ShowPage extends React.Component {
componentWillMount() {
this.resolve(this.props)
this.resolve(this.props);
}
componentWillReceiveProps(nextProps) {
this.resolve(nextProps)
this.resolve(nextProps);
}
resolve(props) {
const {
isResolvingUri,
resolveUri,
claim,
uri,
} = props
const { isResolvingUri, resolveUri, claim, uri } = props;
if(!isResolvingUri && claim === undefined && uri) {
resolveUri(uri)
if (!isResolvingUri && claim === undefined && uri) {
resolveUri(uri);
}
}
render() {
const {
claim,
uri,
isResolvingUri,
} = this.props
const { claim, uri, isResolvingUri } = this.props;
let innerContent = "";
let innerContent = '';
if (isResolvingUri || !claim) {
innerContent = <section className="card">
innerContent = (
<section className="card">
<div className="card__inner">
<div className="card__title-identity"><h1>{uri}</h1></div>
</div>
<div className="card__content">
{ isResolvingUri && <BusyMessage message={__("Loading magic decentralized data...")} /> }
{ claim === null && <span className="empty">{__("There's nothing at this location.")}</span> }
{isResolvingUri &&
<BusyMessage
message={__('Loading magic decentralized data...')}
/>}
{claim === null &&
<span className="empty">
{__("There's nothing at this location.")}
</span>}
</div>
</section>
}
else if (claim.name.length && claim.name[0] === '@') {
innerContent = <ChannelPage uri={uri} />
}
else if (claim) {
innerContent = <FilePage uri={uri} />
);
} else if (claim.name.length && claim.name[0] === '@') {
innerContent = <ChannelPage uri={uri} />;
} else if (claim) {
innerContent = <FilePage uri={uri} />;
}
return (
<main className="main--single-column">{innerContent}</main>
)
return <main className="main--single-column">{innerContent}</main>;
}
}
export default ShowPage
export default ShowPage;

View file

@ -9,8 +9,8 @@ class StartPage extends React.Component {
render() {
return (
<main className="main--single-column">
<h3>{__("LBRY is Closed")}</h3>
<Link href="lbry://lbry" label={__("Click here to start LBRY")} />
<h3>{__('LBRY is Closed')}</h3>
<Link href="lbry://lbry" label={__('Click here to start LBRY')} />
</main>
);
}

View file

@ -1,18 +1,12 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
selectCurrentPage
} from 'selectors/app'
import {
selectBalance
} from 'selectors/wallet'
import WalletPage from './view'
import React from 'react';
import { connect } from 'react-redux';
import { selectCurrentPage } from 'selectors/app';
import { selectBalance } from 'selectors/wallet';
import WalletPage from './view';
const select = (state) => ({
const select = state => ({
currentPage: selectCurrentPage(state),
balance: selectBalance(state)
})
});
export default connect(select, null)(WalletPage)
export default connect(select, null)(WalletPage);

View file

@ -1,35 +1,30 @@
import React from 'react';
import SubHeader from 'component/subHeader'
import TransactionList from 'component/transactionList'
import WalletAddress from 'component/walletAddress'
import WalletSend from 'component/walletSend'
import SubHeader from 'component/subHeader';
import TransactionList from 'component/transactionList';
import WalletAddress from 'component/walletAddress';
import WalletSend from 'component/walletSend';
import {
CreditAmount
} from 'component/common';
import { CreditAmount } from 'component/common';
const WalletPage = (props) => {
const {
balance,
currentPage
} = props
const WalletPage = props => {
const { balance, currentPage } = props;
return (
<main className="main--single-column">
<SubHeader />
<section className="card">
<div className="card__title-primary">
<h3>{__("Balance")}</h3>
<h3>{__('Balance')}</h3>
</div>
<div className="card__content">
<CreditAmount amount={balance} precision={8} />
</div>
</section>
{ currentPage === 'wallet' ? <TransactionList {...props} /> : '' }
{ currentPage === 'send' ? <WalletSend {...props} /> : '' }
{ currentPage === 'receive' ? <WalletAddress /> : '' }
{currentPage === 'wallet' ? <TransactionList {...props} /> : ''}
{currentPage === 'send' ? <WalletSend {...props} /> : ''}
{currentPage === 'receive' ? <WalletAddress /> : ''}
</main>
)
}
);
};
export default WalletPage;

View file

@ -1,7 +1,7 @@
import * as types from 'constants/action_types'
import lbry from 'lbry'
import * as types from 'constants/action_types';
import lbry from 'lbry';
const reducers = {}
const reducers = {};
const defaultState = {
isLoaded: false,
currentPath: 'discover',
@ -9,42 +9,42 @@ const defaultState = {
upgradeSkipped: sessionStorage.getItem('upgradeSkipped'),
daemonReady: false,
obscureNsfw: !lbry.getClientSetting('showNsfw'),
hasSignature: false,
}
hasSignature: false
};
reducers[types.DAEMON_READY] = function(state, action) {
return Object.assign({}, state, {
daemonReady: true,
})
}
daemonReady: true
});
};
reducers[types.CHANGE_PATH] = function(state, action) {
return Object.assign({}, state, {
currentPath: action.data.path,
})
}
currentPath: action.data.path
});
};
reducers[types.UPGRADE_CANCELLED] = function(state, action) {
return Object.assign({}, state, {
downloadProgress: null,
upgradeDownloadComplete: false,
modal: null,
})
}
modal: null
});
};
reducers[types.UPGRADE_DOWNLOAD_COMPLETED] = function(state, action) {
return Object.assign({}, state, {
downloadPath: action.data.path,
upgradeDownloading: false,
upgradeDownloadCompleted: true
})
}
});
};
reducers[types.UPGRADE_DOWNLOAD_STARTED] = function(state, action) {
return Object.assign({}, state, {
upgradeDownloading: true
})
}
});
};
reducers[types.SKIP_UPGRADE] = function(state, action) {
sessionStorage.setItem('upgradeSkipped', true);
@ -52,78 +52,73 @@ reducers[types.SKIP_UPGRADE] = function(state, action) {
return Object.assign({}, state, {
upgradeSkipped: true,
modal: null
})
}
});
};
reducers[types.UPDATE_VERSION] = function(state, action) {
return Object.assign({}, state, {
version: action.data.version
})
}
});
};
reducers[types.OPEN_MODAL] = function(state, action) {
return Object.assign({}, state, {
modal: action.data.modal,
modalExtraContent: action.data.extraContent
})
}
});
};
reducers[types.CLOSE_MODAL] = function(state, action) {
return Object.assign({}, state, {
modal: undefined,
modalExtraContent: undefined
})
}
});
};
reducers[types.UPGRADE_DOWNLOAD_PROGRESSED] = function(state, action) {
return Object.assign({}, state, {
downloadProgress: action.data.percent
})
}
});
};
reducers[types.DAEMON_READY] = function(state, action) {
return Object.assign({}, state, {
daemonReady: true
})
}
});
};
reducers[types.SHOW_SNACKBAR] = function(state, action) {
const {
message,
linkText,
linkTarget,
isError,
} = action.data
const snackBar = Object.assign({}, state.snackBar)
const snacks = Object.assign([], snackBar.snacks)
const { message, linkText, linkTarget, isError } = action.data;
const snackBar = Object.assign({}, state.snackBar);
const snacks = Object.assign([], snackBar.snacks);
snacks.push({
message,
linkText,
linkTarget,
isError,
})
isError
});
const newSnackBar = Object.assign({}, snackBar, {
snacks,
})
snacks
});
return Object.assign({}, state, {
snackBar: newSnackBar,
})
}
snackBar: newSnackBar
});
};
reducers[types.REMOVE_SNACKBAR_SNACK] = function(state, action) {
const snackBar = Object.assign({}, state.snackBar)
const snacks = Object.assign([], snackBar.snacks)
snacks.shift()
const snackBar = Object.assign({}, state.snackBar);
const snacks = Object.assign([], snackBar.snacks);
snacks.shift();
const newSnackBar = Object.assign({}, snackBar, {
snacks,
})
snacks
});
return Object.assign({}, state, {
snackBar: newSnackBar,
})
}
snackBar: newSnackBar
});
};
export default function reducer(state = defaultState, action) {
const handler = reducers[action.type];

Some files were not shown because too many files have changed in this diff Show more