format with prettier
This commit is contained in:
parent
6f93834990
commit
c8b949f020
124 changed files with 8463 additions and 7383 deletions
|
@ -1,238 +1,235 @@
|
|||
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,
|
||||
selectUpgradeDownloadItem,
|
||||
selectUpgradeFilename,
|
||||
selectPageTitle,
|
||||
selectCurrentPage,
|
||||
selectCurrentParams,
|
||||
} from 'selectors/app'
|
||||
import {
|
||||
doSearch,
|
||||
} from 'actions/search'
|
||||
selectUpdateUrl,
|
||||
selectUpgradeDownloadPath,
|
||||
selectUpgradeDownloadItem,
|
||||
selectUpgradeFilename,
|
||||
selectPageTitle,
|
||||
selectCurrentPage,
|
||||
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)}`
|
||||
return function(dispatch, getState) {
|
||||
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) {
|
||||
return function(dispatch, getState) {
|
||||
dispatch({
|
||||
type: types.CHANGE_PATH,
|
||||
data: {
|
||||
path,
|
||||
}
|
||||
})
|
||||
return function(dispatch, getState) {
|
||||
dispatch({
|
||||
type: types.CHANGE_PATH,
|
||||
data: {
|
||||
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)
|
||||
if (currentPage === 'search') {
|
||||
const params = selectCurrentParams(state)
|
||||
dispatch(doSearch(params.query))
|
||||
}
|
||||
}
|
||||
const currentPage = selectCurrentPage(state);
|
||||
if (currentPage === 'search') {
|
||||
const params = selectCurrentParams(state);
|
||||
dispatch(doSearch(params.query));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function doHistoryBack() {
|
||||
return function(dispatch, getState) {
|
||||
history.back()
|
||||
}
|
||||
return function(dispatch, getState) {
|
||||
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)
|
||||
}
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
export function doOpenModal(modal) {
|
||||
return {
|
||||
type: types.OPEN_MODAL,
|
||||
data: {
|
||||
modal
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: types.OPEN_MODAL,
|
||||
data: {
|
||||
modal
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function doCloseModal() {
|
||||
return {
|
||||
type: types.CLOSE_MODAL,
|
||||
}
|
||||
return {
|
||||
type: types.CLOSE_MODAL
|
||||
};
|
||||
}
|
||||
|
||||
export function doUpdateDownloadProgress(percent) {
|
||||
return {
|
||||
type: types.UPGRADE_DOWNLOAD_PROGRESSED,
|
||||
data: {
|
||||
percent: percent
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: types.UPGRADE_DOWNLOAD_PROGRESSED,
|
||||
data: {
|
||||
percent: percent
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function doSkipUpgrade() {
|
||||
return {
|
||||
type: types.SKIP_UPGRADE
|
||||
}
|
||||
return {
|
||||
type: types.SKIP_UPGRADE
|
||||
};
|
||||
}
|
||||
|
||||
export function doStartUpgrade() {
|
||||
return function(dispatch, getState) {
|
||||
const state = getState()
|
||||
const upgradeDownloadPath = selectUpgradeDownloadPath(state)
|
||||
return function(dispatch, getState) {
|
||||
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()
|
||||
// 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)
|
||||
return function(dispatch, 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);
|
||||
|
||||
let options = {
|
||||
onProgress: (p) => dispatch(doUpdateDownloadProgress(Math.round(p * 100))),
|
||||
directory: dir,
|
||||
};
|
||||
download(remote.getCurrentWindow(), selectUpdateUrl(state), options)
|
||||
.then(downloadItem => {
|
||||
/**
|
||||
let options = {
|
||||
onProgress: p => dispatch(doUpdateDownloadProgress(Math.round(p * 100))),
|
||||
directory: dir
|
||||
};
|
||||
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
|
||||
* too soon.
|
||||
*/
|
||||
|
||||
dispatch({
|
||||
type: types.UPGRADE_DOWNLOAD_COMPLETED,
|
||||
data: {
|
||||
downloadItem,
|
||||
path: path.join(dir, upgradeFilename)
|
||||
}
|
||||
})
|
||||
});
|
||||
dispatch({
|
||||
type: types.UPGRADE_DOWNLOAD_COMPLETED,
|
||||
data: {
|
||||
downloadItem,
|
||||
path: path.join(dir, upgradeFilename)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: types.UPGRADE_DOWNLOAD_STARTED
|
||||
})
|
||||
dispatch({
|
||||
type: types.OPEN_MODAL,
|
||||
data: {
|
||||
modal: 'downloading'
|
||||
}
|
||||
})
|
||||
}
|
||||
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)
|
||||
return function(dispatch, getState) {
|
||||
const state = getState();
|
||||
const upgradeDownloadItem = selectUpgradeDownloadItem(state);
|
||||
|
||||
if (upgradeDownloadItem) {
|
||||
/*
|
||||
if (upgradeDownloadItem) {
|
||||
/*
|
||||
* Right now the remote reference to the download item gets garbage collected as soon as the
|
||||
* the download is over (maybe even earlier), so trying to cancel a finished download may
|
||||
* throw an error.
|
||||
*/
|
||||
try {
|
||||
upgradeDownloadItem.cancel();
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
try {
|
||||
upgradeDownloadItem.cancel();
|
||||
} catch (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()
|
||||
return function(dispatch, getState) {
|
||||
const state = getState();
|
||||
|
||||
lbry.getAppVersionInfo().then(({remoteVersion, upgradeAvailable}) => {
|
||||
if (upgradeAvailable) {
|
||||
dispatch({
|
||||
type: types.UPDATE_VERSION,
|
||||
data: {
|
||||
version: remoteVersion,
|
||||
}
|
||||
})
|
||||
dispatch({
|
||||
type: types.OPEN_MODAL,
|
||||
data: {
|
||||
modal: 'upgrade'
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
lbry.getAppVersionInfo().then(({ remoteVersion, upgradeAvailable }) => {
|
||||
if (upgradeAvailable) {
|
||||
dispatch({
|
||||
type: types.UPDATE_VERSION,
|
||||
data: {
|
||||
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)
|
||||
dispatch({
|
||||
type: types.OPEN_MODAL,
|
||||
data: {
|
||||
modal: 'error',
|
||||
extraContent: errorList
|
||||
}
|
||||
})
|
||||
}
|
||||
return function(dispatch, getState) {
|
||||
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
|
||||
}
|
||||
return {
|
||||
type: types.DAEMON_READY
|
||||
};
|
||||
}
|
||||
|
||||
export function doShowSnackBar(data) {
|
||||
return {
|
||||
type: types.SHOW_SNACKBAR,
|
||||
data,
|
||||
}
|
||||
return {
|
||||
type: types.SHOW_SNACKBAR,
|
||||
data
|
||||
};
|
||||
}
|
||||
|
||||
export function doRemoveSnackBarSnack() {
|
||||
return {
|
||||
type: types.REMOVE_SNACKBAR_SNACK,
|
||||
}
|
||||
return {
|
||||
type: types.REMOVE_SNACKBAR_SNACK
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
return function(dispatch, getState) {
|
||||
const state = getState();
|
||||
const alreadyFetching = !!selectFetchingAvailability(state)[uri];
|
||||
|
||||
if (!alreadyFetching) {
|
||||
dispatch({
|
||||
type: types.FETCH_AVAILABILITY_STARTED,
|
||||
data: {uri}
|
||||
})
|
||||
if (!alreadyFetching) {
|
||||
dispatch({
|
||||
type: types.FETCH_AVAILABILITY_STARTED,
|
||||
data: { uri }
|
||||
});
|
||||
|
||||
lbry.get_availability({uri}).then((availability) => {
|
||||
dispatch({
|
||||
type: types.FETCH_AVAILABILITY_COMPLETED,
|
||||
data: {
|
||||
availability,
|
||||
uri,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
lbry.get_availability({ uri }).then(availability => {
|
||||
dispatch({
|
||||
type: types.FETCH_AVAILABILITY_COMPLETED,
|
||||
data: {
|
||||
availability,
|
||||
uri
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,294 +1,288 @@
|
|||
import * as types from 'constants/action_types'
|
||||
import lbry from 'lbry'
|
||||
import lbryio from 'lbryio'
|
||||
import lbryuri from 'lbryuri'
|
||||
import rewards from 'rewards'
|
||||
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 {
|
||||
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'
|
||||
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';
|
||||
|
||||
export function doResolveUri(uri) {
|
||||
return function(dispatch, getState) {
|
||||
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 }
|
||||
});
|
||||
|
||||
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,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
dispatch({
|
||||
type: types.RESOLVE_URI_COMPLETED,
|
||||
data: {
|
||||
uri,
|
||||
claim,
|
||||
certificate
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function doCancelResolveUri(uri) {
|
||||
return function(dispatch, getState) {
|
||||
lbry.cancelResolve({ uri })
|
||||
dispatch({
|
||||
type: types.RESOLVE_URI_CANCELED,
|
||||
data: { uri }
|
||||
})
|
||||
}
|
||||
return function(dispatch, getState) {
|
||||
lbry.cancelResolve({ uri });
|
||||
dispatch({
|
||||
type: types.RESOLVE_URI_CANCELED,
|
||||
data: { uri }
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doFetchFeaturedUris() {
|
||||
return function(dispatch, getState) {
|
||||
const state = getState()
|
||||
return function(dispatch, getState) {
|
||||
const state = getState();
|
||||
|
||||
dispatch({
|
||||
type: types.FETCH_FEATURED_CONTENT_STARTED,
|
||||
})
|
||||
dispatch({
|
||||
type: types.FETCH_FEATURED_CONTENT_STARTED
|
||||
});
|
||||
|
||||
const success = ({ Categories, Uris }) => {
|
||||
const success = ({ Categories, Uris }) => {
|
||||
let featuredUris = {};
|
||||
|
||||
let featuredUris = {}
|
||||
Categories.forEach(category => {
|
||||
if (Uris[category] && Uris[category].length) {
|
||||
featuredUris[category] = Uris[category];
|
||||
}
|
||||
});
|
||||
|
||||
Categories.forEach((category) => {
|
||||
if (Uris[category] && Uris[category].length) {
|
||||
featuredUris[category] = Uris[category]
|
||||
}
|
||||
})
|
||||
dispatch({
|
||||
type: types.FETCH_FEATURED_CONTENT_COMPLETED,
|
||||
data: {
|
||||
categories: Categories,
|
||||
uris: featuredUris
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: types.FETCH_FEATURED_CONTENT_COMPLETED,
|
||||
data: {
|
||||
categories: Categories,
|
||||
uris: featuredUris,
|
||||
}
|
||||
})
|
||||
}
|
||||
const failure = () => {
|
||||
dispatch({
|
||||
type: types.FETCH_FEATURED_CONTENT_COMPLETED,
|
||||
data: {
|
||||
categories: [],
|
||||
uris: {}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const failure = () => {
|
||||
dispatch({
|
||||
type: types.FETCH_FEATURED_CONTENT_COMPLETED,
|
||||
data: {
|
||||
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()
|
||||
return function(dispatch, getState) {
|
||||
const state = getState();
|
||||
|
||||
lbry.file_list({
|
||||
outpoint: outpoint,
|
||||
full_status: true,
|
||||
}).then(([fileInfo]) => {
|
||||
if(!fileInfo || fileInfo.written_bytes == 0) {
|
||||
// download hasn't started yet
|
||||
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
|
||||
dispatch({
|
||||
type: types.DOWNLOADING_COMPLETED,
|
||||
data: {
|
||||
uri,
|
||||
outpoint,
|
||||
fileInfo,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// ready to play
|
||||
const {
|
||||
total_bytes,
|
||||
written_bytes,
|
||||
} = fileInfo
|
||||
const progress = (written_bytes / total_bytes) * 100
|
||||
lbry
|
||||
.file_list({
|
||||
outpoint: outpoint,
|
||||
full_status: true
|
||||
})
|
||||
.then(([fileInfo]) => {
|
||||
if (!fileInfo || fileInfo.written_bytes == 0) {
|
||||
// download hasn't started yet
|
||||
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
|
||||
dispatch({
|
||||
type: types.DOWNLOADING_COMPLETED,
|
||||
data: {
|
||||
uri,
|
||||
outpoint,
|
||||
fileInfo
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// ready to play
|
||||
const { total_bytes, written_bytes } = fileInfo;
|
||||
const progress = written_bytes / total_bytes * 100;
|
||||
|
||||
dispatch({
|
||||
type: types.DOWNLOADING_PROGRESSED,
|
||||
data: {
|
||||
uri,
|
||||
outpoint,
|
||||
fileInfo,
|
||||
progress,
|
||||
}
|
||||
})
|
||||
setTimeout(() => { dispatch(doUpdateLoadStatus(uri, outpoint)) }, 250)
|
||||
}
|
||||
})
|
||||
}
|
||||
dispatch({
|
||||
type: types.DOWNLOADING_PROGRESSED,
|
||||
data: {
|
||||
uri,
|
||||
outpoint,
|
||||
fileInfo,
|
||||
progress
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
dispatch(doUpdateLoadStatus(uri, outpoint));
|
||||
}, 250);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doDownloadFile(uri, streamInfo) {
|
||||
return function(dispatch, getState) {
|
||||
const state = getState()
|
||||
return function(dispatch, getState) {
|
||||
const state = getState();
|
||||
|
||||
lbry.file_list({ outpoint: streamInfo.outpoint, full_status: true }).then(([fileInfo]) => {
|
||||
dispatch({
|
||||
type: types.DOWNLOADING_STARTED,
|
||||
data: {
|
||||
uri,
|
||||
outpoint: streamInfo.outpoint,
|
||||
fileInfo,
|
||||
}
|
||||
})
|
||||
lbry
|
||||
.file_list({ outpoint: streamInfo.outpoint, full_status: true })
|
||||
.then(([fileInfo]) => {
|
||||
dispatch({
|
||||
type: types.DOWNLOADING_STARTED,
|
||||
data: {
|
||||
uri,
|
||||
outpoint: streamInfo.outpoint,
|
||||
fileInfo
|
||||
}
|
||||
});
|
||||
|
||||
dispatch(doUpdateLoadStatus(uri, streamInfo.outpoint))
|
||||
})
|
||||
dispatch(doUpdateLoadStatus(uri, streamInfo.outpoint));
|
||||
});
|
||||
|
||||
lbryio.call('file', 'view', {
|
||||
uri: uri,
|
||||
outpoint: streamInfo.outpoint,
|
||||
claim_id: streamInfo.claim_id,
|
||||
}).catch(() => {})
|
||||
lbryio
|
||||
.call('file', 'view', {
|
||||
uri: uri,
|
||||
outpoint: streamInfo.outpoint,
|
||||
claim_id: streamInfo.claim_id
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
rewards.claimEligiblePurchaseRewards()
|
||||
|
||||
}
|
||||
rewards.claimEligiblePurchaseRewards();
|
||||
};
|
||||
}
|
||||
|
||||
export function doLoadVideo(uri) {
|
||||
return function(dispatch, getState) {
|
||||
const state = getState()
|
||||
return function(dispatch, getState) {
|
||||
const state = getState();
|
||||
|
||||
dispatch({
|
||||
type: types.LOADING_VIDEO_STARTED,
|
||||
data: {
|
||||
uri
|
||||
}
|
||||
})
|
||||
dispatch({
|
||||
type: types.LOADING_VIDEO_STARTED,
|
||||
data: {
|
||||
uri
|
||||
}
|
||||
});
|
||||
|
||||
lbry.get({ uri }).then(streamInfo => {
|
||||
const timeout = streamInfo === null ||
|
||||
typeof streamInfo !== 'object' ||
|
||||
streamInfo.error == 'Timeout'
|
||||
lbry.get({ uri }).then(streamInfo => {
|
||||
const timeout =
|
||||
streamInfo === null ||
|
||||
typeof streamInfo !== 'object' ||
|
||||
streamInfo.error == 'Timeout';
|
||||
|
||||
if(timeout) {
|
||||
dispatch({
|
||||
type: types.LOADING_VIDEO_FAILED,
|
||||
data: { uri }
|
||||
})
|
||||
dispatch(doOpenModal('timedOut'))
|
||||
} else {
|
||||
dispatch(doDownloadFile(uri, streamInfo))
|
||||
}
|
||||
})
|
||||
}
|
||||
if (timeout) {
|
||||
dispatch({
|
||||
type: types.LOADING_VIDEO_FAILED,
|
||||
data: { uri }
|
||||
});
|
||||
dispatch(doOpenModal('timedOut'));
|
||||
} else {
|
||||
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]
|
||||
return function(dispatch, getState) {
|
||||
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))
|
||||
// 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));
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// we are already downloading the file
|
||||
if (alreadyDownloading) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
// we are already downloading the file
|
||||
if (alreadyDownloading) {
|
||||
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()
|
||||
}
|
||||
// the file is free or we have partially downloaded it
|
||||
if (cost <= 0.01 || (fileInfo && fileInfo.download_directory)) {
|
||||
dispatch(doLoadVideo(uri));
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (cost > balance) {
|
||||
dispatch(doOpenModal('notEnoughCredits'))
|
||||
} else {
|
||||
dispatch(doOpenModal(purchaseModalName))
|
||||
}
|
||||
if (cost > balance) {
|
||||
dispatch(doOpenModal('notEnoughCredits'));
|
||||
} else {
|
||||
dispatch(doOpenModal(purchaseModalName));
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
}
|
||||
|
||||
export function doFetchClaimsByChannel(uri) {
|
||||
return function(dispatch, getState) {
|
||||
dispatch({
|
||||
type: types.FETCH_CHANNEL_CLAIMS_STARTED,
|
||||
data: { uri }
|
||||
})
|
||||
return function(dispatch, getState) {
|
||||
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,
|
||||
data: {
|
||||
uri,
|
||||
claims: claims_in_channel
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
dispatch({
|
||||
type: types.FETCH_CHANNEL_CLAIMS_COMPLETED,
|
||||
data: {
|
||||
uri,
|
||||
claims: claims_in_channel
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doFetchClaimListMine() {
|
||||
return function(dispatch, getState) {
|
||||
dispatch({
|
||||
type: types.FETCH_CLAIM_LIST_MINE_STARTED
|
||||
})
|
||||
return function(dispatch, getState) {
|
||||
dispatch({
|
||||
type: types.FETCH_CLAIM_LIST_MINE_STARTED
|
||||
});
|
||||
|
||||
|
||||
lbry.claim_list_mine().then((claims) => {
|
||||
dispatch({
|
||||
type: types.FETCH_CLAIM_LIST_MINE_COMPLETED,
|
||||
data: {
|
||||
claims
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
lbry.claim_list_mine().then(claims => {
|
||||
dispatch({
|
||||
type: types.FETCH_CLAIM_LIST_MINE_COMPLETED,
|
||||
data: {
|
||||
claims
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,77 +1,68 @@
|
|||
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)
|
||||
return function(dispatch, getState) {
|
||||
const state = getState(),
|
||||
claim = selectClaimsByUri(state)[uri],
|
||||
isResolving = selectResolvingUris(state).indexOf(uri) !== -1,
|
||||
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)
|
||||
if (!isResolving) {
|
||||
dispatch(doResolveUri(uri))
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!claim) {
|
||||
setTimeout(() => {
|
||||
dispatch(doFetchCostInfoForUri(uri));
|
||||
}, 1000);
|
||||
if (!isResolving) {
|
||||
dispatch(doResolveUri(uri));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function begin() {
|
||||
dispatch({
|
||||
type: types.FETCH_COST_INFO_STARTED,
|
||||
data: {
|
||||
uri
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function begin() {
|
||||
dispatch({
|
||||
type: types.FETCH_COST_INFO_STARTED,
|
||||
data: {
|
||||
uri,
|
||||
}
|
||||
})
|
||||
}
|
||||
function resolve(costInfo) {
|
||||
dispatch({
|
||||
type: types.FETCH_COST_INFO_COMPLETED,
|
||||
data: {
|
||||
uri,
|
||||
costInfo
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resolve(costInfo) {
|
||||
dispatch({
|
||||
type: types.FETCH_COST_INFO_COMPLETED,
|
||||
data: {
|
||||
uri,
|
||||
costInfo,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (isGenerous && claim) {
|
||||
let cost
|
||||
const fee = claim.value.stream.metadata.fee;
|
||||
if (fee === undefined ) {
|
||||
resolve({ cost: 0, includesData: true })
|
||||
} else if (fee.currency == 'LBC') {
|
||||
resolve({ cost: fee.amount, includesData: true })
|
||||
} else {
|
||||
begin()
|
||||
lbryio.getExchangeRates().then(({lbc_usd}) => {
|
||||
resolve({ cost: fee.amount / lbc_usd, includesData: true })
|
||||
});
|
||||
}
|
||||
} else {
|
||||
begin()
|
||||
lbry.getCostInfo(uri).then(resolve)
|
||||
}
|
||||
}
|
||||
if (isGenerous && claim) {
|
||||
let cost;
|
||||
const fee = claim.value.stream.metadata.fee;
|
||||
if (fee === undefined) {
|
||||
resolve({ cost: 0, includesData: true });
|
||||
} else if (fee.currency == 'LBC') {
|
||||
resolve({ cost: fee.amount, includesData: true });
|
||||
} else {
|
||||
begin();
|
||||
lbryio.getExchangeRates().then(({ lbc_usd }) => {
|
||||
resolve({ cost: fee.amount / lbc_usd, includesData: true });
|
||||
});
|
||||
}
|
||||
} else {
|
||||
begin();
|
||||
lbry.getCostInfo(uri).then(resolve);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,121 +1,113 @@
|
|||
import * as types from 'constants/action_types'
|
||||
import lbry from 'lbry'
|
||||
import * as types from 'constants/action_types';
|
||||
import lbry from 'lbry';
|
||||
import { doFetchClaimListMine } from 'actions/content';
|
||||
import {
|
||||
doFetchClaimListMine
|
||||
} from 'actions/content'
|
||||
selectClaimsByUri,
|
||||
selectClaimListMineIsPending
|
||||
} from 'selectors/claims';
|
||||
import {
|
||||
selectClaimsByUri,
|
||||
selectClaimListMineIsPending,
|
||||
} from 'selectors/claims'
|
||||
import {
|
||||
selectFileListIsPending,
|
||||
selectAllFileInfos,
|
||||
selectUrisLoading,
|
||||
} from 'selectors/file_info'
|
||||
import {
|
||||
doCloseModal,
|
||||
} from 'actions/app'
|
||||
selectFileListIsPending,
|
||||
selectAllFileInfos,
|
||||
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]
|
||||
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];
|
||||
|
||||
if (!alreadyFetching) {
|
||||
dispatch({
|
||||
type: types.FETCH_FILE_INFO_STARTED,
|
||||
data: {
|
||||
outpoint,
|
||||
}
|
||||
})
|
||||
if (!alreadyFetching) {
|
||||
dispatch({
|
||||
type: types.FETCH_FILE_INFO_STARTED,
|
||||
data: {
|
||||
outpoint
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function doFileList() {
|
||||
return function(dispatch, getState) {
|
||||
const state = getState()
|
||||
const isPending = selectFileListIsPending(state)
|
||||
return function(dispatch, getState) {
|
||||
const state = getState();
|
||||
const isPending = selectFileListIsPending(state);
|
||||
|
||||
if (!isPending) {
|
||||
dispatch({
|
||||
type: types.FILE_LIST_STARTED,
|
||||
})
|
||||
if (!isPending) {
|
||||
dispatch({
|
||||
type: types.FILE_LIST_STARTED
|
||||
});
|
||||
|
||||
lbry.file_list().then((fileInfos) => {
|
||||
dispatch({
|
||||
type: types.FILE_LIST_COMPLETED,
|
||||
data: {
|
||||
fileInfos,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
lbry.file_list().then(fileInfos => {
|
||||
dispatch({
|
||||
type: types.FILE_LIST_COMPLETED,
|
||||
data: {
|
||||
fileInfos
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function doOpenFileInShell(fileInfo) {
|
||||
return function(dispatch, getState) {
|
||||
shell.openItem(fileInfo.download_path)
|
||||
}
|
||||
return function(dispatch, getState) {
|
||||
shell.openItem(fileInfo.download_path);
|
||||
};
|
||||
}
|
||||
|
||||
export function doOpenFileInFolder(fileInfo) {
|
||||
return function(dispatch, getState) {
|
||||
shell.showItemInFolder(fileInfo.download_path)
|
||||
}
|
||||
return function(dispatch, getState) {
|
||||
shell.showItemInFolder(fileInfo.download_path);
|
||||
};
|
||||
}
|
||||
|
||||
export function doDeleteFile(outpoint, deleteFromComputer) {
|
||||
return function(dispatch, getState) {
|
||||
return function(dispatch, getState) {
|
||||
dispatch({
|
||||
type: types.FILE_DELETE,
|
||||
data: {
|
||||
outpoint
|
||||
}
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: types.FILE_DELETE,
|
||||
data: {
|
||||
outpoint
|
||||
}
|
||||
})
|
||||
lbry.file_delete({
|
||||
outpoint: outpoint,
|
||||
delete_target_file: deleteFromComputer
|
||||
});
|
||||
|
||||
lbry.file_delete({
|
||||
outpoint: outpoint,
|
||||
delete_target_file: deleteFromComputer,
|
||||
})
|
||||
|
||||
dispatch(doCloseModal())
|
||||
}
|
||||
dispatch(doCloseModal());
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function doFetchFileInfosAndPublishedClaims() {
|
||||
return function(dispatch, getState) {
|
||||
const state = getState(),
|
||||
isClaimListMinePending = selectClaimListMineIsPending(state),
|
||||
isFileInfoListPending = selectFileListIsPending(state)
|
||||
return function(dispatch, getState) {
|
||||
const state = getState(),
|
||||
isClaimListMinePending = selectClaimListMineIsPending(state),
|
||||
isFileInfoListPending = selectFileListIsPending(state);
|
||||
|
||||
if (isClaimListMinePending === undefined) {
|
||||
dispatch(doFetchClaimListMine())
|
||||
}
|
||||
if (isClaimListMinePending === undefined) {
|
||||
dispatch(doFetchClaimListMine());
|
||||
}
|
||||
|
||||
if (isFileInfoListPending === undefined) {
|
||||
dispatch(doFileList())
|
||||
}
|
||||
}
|
||||
if (isFileInfoListPending === undefined) {
|
||||
dispatch(doFileList());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
return function(dispatch, getState) {
|
||||
const state = getState();
|
||||
|
||||
dispatch({
|
||||
type: types.FETCH_REWARDS_STARTED,
|
||||
})
|
||||
dispatch({
|
||||
type: types.FETCH_REWARDS_STARTED
|
||||
});
|
||||
|
||||
lbryio.call('reward', 'list', {}).then(function(userRewards) {
|
||||
dispatch({
|
||||
type: types.FETCH_REWARDS_COMPLETED,
|
||||
data: { userRewards }
|
||||
})
|
||||
});
|
||||
}
|
||||
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])
|
||||
dispatch({
|
||||
type: types.REWARD_CLAIMED,
|
||||
data: {
|
||||
reward: rewards[rewardType]
|
||||
}
|
||||
})
|
||||
} catch(err) {
|
||||
}
|
||||
}
|
||||
return function(dispatch, getState) {
|
||||
try {
|
||||
rewards.claimReward(rewards[rewardType]);
|
||||
dispatch({
|
||||
type: types.REWARD_CLAIMED,
|
||||
data: {
|
||||
reward: rewards[rewardType]
|
||||
}
|
||||
});
|
||||
} catch (err) {}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
return function(dispatch, getState) {
|
||||
const state = getState();
|
||||
const page = selectCurrentPage(state);
|
||||
|
||||
if (!query) {
|
||||
return dispatch({
|
||||
type: types.SEARCH_CANCELLED,
|
||||
})
|
||||
}
|
||||
if (!query) {
|
||||
return dispatch({
|
||||
type: types.SEARCH_CANCELLED
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: types.SEARCH_STARTED,
|
||||
data: { query }
|
||||
})
|
||||
dispatch({
|
||||
type: types.SEARCH_STARTED,
|
||||
data: { 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))
|
||||
})
|
||||
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));
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: types.SEARCH_COMPLETED,
|
||||
data: {
|
||||
query,
|
||||
results,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
dispatch({
|
||||
type: types.SEARCH_COMPLETED,
|
||||
data: {
|
||||
query,
|
||||
results
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
dispatch({
|
||||
type: types.DAEMON_SETTINGS_RECEIVED,
|
||||
data: {
|
||||
settings
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
return function(dispatch, getState) {
|
||||
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) => {
|
||||
dispatch({
|
||||
type: types.DAEMON_SETTINGS_RECEIVED,
|
||||
data: {
|
||||
settings
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
return function(dispatch, getState) {
|
||||
let settings = {};
|
||||
settings[key] = value;
|
||||
lbry.settings_set(settings).then(settings);
|
||||
lbry.settings_get().then(settings => {
|
||||
dispatch({
|
||||
type: types.DAEMON_SETTINGS_RECEIVED,
|
||||
data: {
|
||||
settings
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,125 +1,127 @@
|
|||
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'
|
||||
selectDraftTransaction,
|
||||
selectDraftTransactionAmount,
|
||||
selectBalance
|
||||
} from 'selectors/wallet';
|
||||
import { doOpenModal } from 'actions/app';
|
||||
|
||||
export function doUpdateBalance(balance) {
|
||||
return {
|
||||
type: types.UPDATE_BALANCE,
|
||||
data: {
|
||||
balance: balance
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: types.UPDATE_BALANCE,
|
||||
data: {
|
||||
balance: balance
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function doFetchTransactions() {
|
||||
return function(dispatch, getState) {
|
||||
dispatch({
|
||||
type: types.FETCH_TRANSACTIONS_STARTED
|
||||
})
|
||||
return function(dispatch, getState) {
|
||||
dispatch({
|
||||
type: types.FETCH_TRANSACTIONS_STARTED
|
||||
});
|
||||
|
||||
lbry.call('transaction_list', {}, (results) => {
|
||||
dispatch({
|
||||
type: types.FETCH_TRANSACTIONS_COMPLETED,
|
||||
data: {
|
||||
transactions: 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
|
||||
})
|
||||
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 }
|
||||
})
|
||||
})
|
||||
}
|
||||
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
|
||||
})
|
||||
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
|
||||
})
|
||||
})
|
||||
}
|
||||
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)
|
||||
return function(dispatch, getState) {
|
||||
const state = getState();
|
||||
const draftTx = selectDraftTransaction(state);
|
||||
const balance = selectBalance(state);
|
||||
const amount = selectDraftTransactionAmount(state);
|
||||
|
||||
if (balance - amount < 1) {
|
||||
return dispatch(doOpenModal('insufficientBalance'))
|
||||
}
|
||||
if (balance - amount < 1) {
|
||||
return dispatch(doOpenModal('insufficientBalance'));
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: types.SEND_TRANSACTION_STARTED,
|
||||
})
|
||||
dispatch({
|
||||
type: types.SEND_TRANSACTION_STARTED
|
||||
});
|
||||
|
||||
const successCallback = (results) => {
|
||||
if(results === true) {
|
||||
dispatch({
|
||||
type: types.SEND_TRANSACTION_COMPLETED,
|
||||
})
|
||||
dispatch(doOpenModal('transactionSuccessful'))
|
||||
}
|
||||
else {
|
||||
dispatch({
|
||||
type: types.SEND_TRANSACTION_FAILED,
|
||||
data: { error: results }
|
||||
})
|
||||
dispatch(doOpenModal('transactionFailed'))
|
||||
}
|
||||
}
|
||||
const successCallback = results => {
|
||||
if (results === true) {
|
||||
dispatch({
|
||||
type: types.SEND_TRANSACTION_COMPLETED
|
||||
});
|
||||
dispatch(doOpenModal('transactionSuccessful'));
|
||||
} else {
|
||||
dispatch({
|
||||
type: types.SEND_TRANSACTION_FAILED,
|
||||
data: { error: results }
|
||||
});
|
||||
dispatch(doOpenModal('transactionFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
const errorCallback = (error) => {
|
||||
dispatch({
|
||||
type: types.SEND_TRANSACTION_FAILED,
|
||||
data: { error: error.message }
|
||||
})
|
||||
dispatch(doOpenModal('transactionFailed'))
|
||||
}
|
||||
const errorCallback = error => {
|
||||
dispatch({
|
||||
type: types.SEND_TRANSACTION_FAILED,
|
||||
data: { error: error.message }
|
||||
});
|
||||
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 }
|
||||
}
|
||||
return {
|
||||
type: types.SET_DRAFT_TRANSACTION_AMOUNT,
|
||||
data: { amount }
|
||||
};
|
||||
}
|
||||
|
||||
export function doSetDraftTransactionAddress(address) {
|
||||
return {
|
||||
type: types.SET_DRAFT_TRANSACTION_ADDRESS,
|
||||
data: { address }
|
||||
}
|
||||
return {
|
||||
type: types.SET_DRAFT_TRANSACTION_ADDRESS,
|
||||
data: { address }
|
||||
};
|
||||
}
|
||||
|
|
30
ui/js/app.js
30
ui/js/app.js
|
@ -3,20 +3,26 @@ 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,
|
||||
config: config,
|
||||
store: store,
|
||||
i18n: i18n,
|
||||
logs: logs,
|
||||
log: function(message) {
|
||||
console.log(message);
|
||||
logs.push(message);
|
||||
}
|
||||
}
|
||||
env: env,
|
||||
config: config,
|
||||
store: store,
|
||||
i18n: i18n,
|
||||
logs: logs,
|
||||
log: function(message) {
|
||||
console.log(message);
|
||||
logs.push(message);
|
||||
}
|
||||
};
|
||||
|
||||
window.__ = i18n.__;
|
||||
window.__n = i18n.__n;
|
||||
|
|
|
@ -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)),
|
||||
checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()),
|
||||
updateBalance: (balance) => dispatch(doUpdateBalance(balance))
|
||||
})
|
||||
const perform = dispatch => ({
|
||||
alertError: errorList => dispatch(doAlertError(errorList)),
|
||||
checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()),
|
||||
updateBalance: balance => dispatch(doUpdateBalance(balance))
|
||||
});
|
||||
|
||||
export default connect(select, perform)(App)
|
||||
export default connect(select, perform)(App);
|
||||
|
|
|
@ -1,42 +1,42 @@
|
|||
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) => {
|
||||
this.props.alertError(event.detail);
|
||||
});
|
||||
componentWillMount() {
|
||||
document.addEventListener('unhandledError', event => {
|
||||
this.props.alertError(event.detail);
|
||||
});
|
||||
|
||||
if (!this.props.upgradeSkipped) {
|
||||
this.props.checkUpgradeAvailable()
|
||||
}
|
||||
if (!this.props.upgradeSkipped) {
|
||||
this.props.checkUpgradeAvailable();
|
||||
}
|
||||
|
||||
lbry.balanceSubscribe((balance) => {
|
||||
this.props.updateBalance(balance)
|
||||
})
|
||||
}
|
||||
lbry.balanceSubscribe(balance => {
|
||||
this.props.updateBalance(balance);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
modal,
|
||||
} = this.props
|
||||
render() {
|
||||
const { modal } = this.props;
|
||||
|
||||
return <div id="window">
|
||||
<Header />
|
||||
<div id="main-content">
|
||||
<Router />
|
||||
</div>
|
||||
{modal == 'upgrade' && <UpgradeModal />}
|
||||
{modal == 'downloading' && <DownloadingModal />}
|
||||
{modal == 'error' && <ErrorModal />}
|
||||
</div>
|
||||
}
|
||||
return (
|
||||
<div id="window">
|
||||
<Header />
|
||||
<div id="main-content">
|
||||
<Router />
|
||||
</div>
|
||||
{modal == 'upgrade' && <UpgradeModal />}
|
||||
{modal == 'downloading' && <DownloadingModal />}
|
||||
{modal == 'error' && <ErrorModal />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default App
|
||||
export default App;
|
||||
|
|
|
@ -1,327 +1,517 @@
|
|||
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) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
rewardType: null,
|
||||
email: '',
|
||||
submitting: false
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
rewardType: null,
|
||||
email: '',
|
||||
submitting: false
|
||||
};
|
||||
}
|
||||
|
||||
handleEmailChanged(event) {
|
||||
this.setState({
|
||||
email: event.target.value,
|
||||
});
|
||||
}
|
||||
handleEmailChanged(event) {
|
||||
this.setState({
|
||||
email: event.target.value
|
||||
});
|
||||
}
|
||||
|
||||
onEmailSaved(email) {
|
||||
this.props.setStage("confirm", { email: email })
|
||||
}
|
||||
onEmailSaved(email) {
|
||||
this.props.setStage('confirm', { email: email });
|
||||
}
|
||||
|
||||
handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.setState({
|
||||
submitting: true,
|
||||
});
|
||||
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"))) {
|
||||
this.onEmailSaved(this.state.email);
|
||||
return;
|
||||
} else if (this._emailRow) {
|
||||
this._emailRow.showError(error.message)
|
||||
}
|
||||
this.setState({ submitting: false });
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
submitting: true
|
||||
});
|
||||
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'))
|
||||
) {
|
||||
this.onEmailSaved(this.state.email);
|
||||
return;
|
||||
} else if (this._emailRow) {
|
||||
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) }} />
|
||||
<div className="form-row-submit">
|
||||
<Link button="primary" label={__("Next")} disabled={this.state.submitting} onClick={(event) => { this.handleSubmit(event) }} />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
<div className="form-row-submit">
|
||||
<Link
|
||||
button="primary"
|
||||
label={__('Next')}
|
||||
disabled={this.state.submitting}
|
||||
onClick={event => {
|
||||
this.handleSubmit(event);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ConfirmEmailStage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
rewardType: null,
|
||||
code: '',
|
||||
submitting: false,
|
||||
errorMessage: null,
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
rewardType: null,
|
||||
code: '',
|
||||
submitting: false,
|
||||
errorMessage: null
|
||||
};
|
||||
}
|
||||
|
||||
handleCodeChanged(event) {
|
||||
this.setState({
|
||||
code: event.target.value,
|
||||
});
|
||||
}
|
||||
handleCodeChanged(event) {
|
||||
this.setState({
|
||||
code: event.target.value
|
||||
});
|
||||
}
|
||||
|
||||
handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
this.setState({
|
||||
submitting: true,
|
||||
});
|
||||
handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
this.setState({
|
||||
submitting: true
|
||||
});
|
||||
|
||||
const onSubmitError = (error) => {
|
||||
if (this._codeRow) {
|
||||
this._codeRow.showError(error.message)
|
||||
}
|
||||
this.setState({ submitting: false });
|
||||
};
|
||||
const onSubmitError = error => {
|
||||
if (this._codeRow) {
|
||||
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) => {
|
||||
if (userEmail.is_verified) {
|
||||
this.props.setStage("welcome")
|
||||
} else {
|
||||
onSubmitError(new Error(__("Your email is still not verified."))) //shouldn't happen?
|
||||
}
|
||||
}, onSubmitError);
|
||||
}
|
||||
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');
|
||||
} else {
|
||||
onSubmitError(new Error(__('Your email is still not verified.'))); //shouldn't happen?
|
||||
}
|
||||
}, onSubmitError);
|
||||
}
|
||||
|
||||
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.")}/>
|
||||
<div className="form-row-submit form-row-submit--with-footer">
|
||||
<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")} />.
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
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.'
|
||||
)}
|
||||
/>
|
||||
<div className="form-row-submit form-row-submit--with-footer">
|
||||
<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')}
|
||||
/>.
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WelcomeStage extends React.Component {
|
||||
static propTypes = {
|
||||
endAuth: React.PropTypes.func,
|
||||
}
|
||||
static propTypes = {
|
||||
endAuth: React.PropTypes.func
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
hasReward: false,
|
||||
rewardAmount: null,
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
hasReward: false,
|
||||
rewardAmount: null
|
||||
};
|
||||
}
|
||||
|
||||
onRewardClaim(reward) {
|
||||
this.setState({
|
||||
hasReward: true,
|
||||
rewardAmount: reward.amount
|
||||
})
|
||||
}
|
||||
onRewardClaim(reward) {
|
||||
this.setState({
|
||||
hasReward: true,
|
||||
rewardAmount: reward.amount
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
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) }} />
|
||||
</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>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
</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>;
|
||||
}
|
||||
}
|
||||
|
||||
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() } } />
|
||||
</section>
|
||||
}
|
||||
|
||||
const PendingStage = (props) => {
|
||||
return <section>
|
||||
<p>{__("Preparing for first access")} <span className="busy-indicator"></span></p>
|
||||
</section>
|
||||
}
|
||||
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();
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
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);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._balanceSubscribeId = null
|
||||
this._balanceSubscribeId = null;
|
||||
|
||||
this.state = {
|
||||
balance: 0,
|
||||
address: getLocal('wallet_address')
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
balance: 0,
|
||||
address: getLocal('wallet_address')
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this._balanceSubscribeId = lbry.balanceSubscribe((balance) => {
|
||||
this.setState({
|
||||
balance: balance
|
||||
});
|
||||
})
|
||||
componentWillMount() {
|
||||
this._balanceSubscribeId = lbry.balanceSubscribe(balance => {
|
||||
this.setState({
|
||||
balance: balance
|
||||
});
|
||||
});
|
||||
|
||||
if (!this.state.address) {
|
||||
lbry.wallet_unused_address().then((address) => {
|
||||
setLocal('wallet_address', address);
|
||||
this.setState({ address: address });
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!this.state.address) {
|
||||
lbry.wallet_unused_address().then(address => {
|
||||
setLocal('wallet_address', address);
|
||||
this.setState({ address: address });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._balanceSubscribeId) {
|
||||
lbry.balanceUnsubscribe(this._balanceSubscribeId)
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
if (this._balanceSubscribeId) {
|
||||
lbry.balanceUnsubscribe(this._balanceSubscribeId);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const disabled = this.state.balance < 1;
|
||||
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>{__("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>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
const disabled = this.state.balance < 1;
|
||||
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>{__("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>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class AuthOverlay extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._stages = {
|
||||
pending: PendingStage,
|
||||
error: ErrorStage,
|
||||
nocode: CodeRequiredStage,
|
||||
email: SubmitEmailStage,
|
||||
confirm: ConfirmEmailStage,
|
||||
welcome: WelcomeStage
|
||||
}
|
||||
this._stages = {
|
||||
pending: PendingStage,
|
||||
error: ErrorStage,
|
||||
nocode: CodeRequiredStage,
|
||||
email: SubmitEmailStage,
|
||||
confirm: ConfirmEmailStage,
|
||||
welcome: WelcomeStage
|
||||
};
|
||||
|
||||
this.state = {
|
||||
stage: "pending",
|
||||
stageProps: {}
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
stage: 'pending',
|
||||
stageProps: {}
|
||||
};
|
||||
}
|
||||
|
||||
setStage(stage, stageProps = {}) {
|
||||
this.setState({
|
||||
stage: stage,
|
||||
stageProps: stageProps
|
||||
})
|
||||
}
|
||||
setStage(stage, stageProps = {}) {
|
||||
this.setState({
|
||||
stage: stage,
|
||||
stageProps: stageProps
|
||||
});
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
lbryio.authenticate().then((user) => {
|
||||
if (!user.has_verified_email) {
|
||||
if (getLocal('auth_bypassed')) {
|
||||
this.setStage(null)
|
||||
} else {
|
||||
this.setStage("email", {})
|
||||
}
|
||||
} else {
|
||||
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")
|
||||
});
|
||||
}
|
||||
}).catch((err) => {
|
||||
this.setStage("error", { errorText: err.message })
|
||||
document.dispatchEvent(new CustomEvent('unhandledError', {
|
||||
detail: {
|
||||
message: err.message,
|
||||
data: err.stack
|
||||
}
|
||||
}));
|
||||
})
|
||||
}
|
||||
componentWillMount() {
|
||||
lbryio
|
||||
.authenticate()
|
||||
.then(user => {
|
||||
if (!user.has_verified_email) {
|
||||
if (getLocal('auth_bypassed')) {
|
||||
this.setStage(null);
|
||||
} else {
|
||||
this.setStage('email', {});
|
||||
}
|
||||
} else {
|
||||
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');
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.setStage('error', { errorText: err.message });
|
||||
document.dispatchEvent(
|
||||
new CustomEvent('unhandledError', {
|
||||
detail: {
|
||||
message: err.message,
|
||||
data: err.stack
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.stage) {
|
||||
return null;
|
||||
}
|
||||
const StageContent = this._stages[this.state.stage];
|
||||
render() {
|
||||
if (!this.state.stage) {
|
||||
return null;
|
||||
}
|
||||
const StageContent = this._stages[this.state.stage];
|
||||
|
||||
if (!StageContent) {
|
||||
return <span className="empty">{__("Unknown authentication step.")}</span>
|
||||
}
|
||||
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} />
|
||||
);
|
||||
}
|
||||
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}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,155 +3,202 @@ import lbry from '../lbry.js';
|
|||
|
||||
//component/icon.js
|
||||
export class Icon extends React.Component {
|
||||
static propTypes = {
|
||||
icon: React.PropTypes.string.isRequired,
|
||||
className: React.PropTypes.string,
|
||||
fixed: React.PropTypes.bool,
|
||||
}
|
||||
static propTypes = {
|
||||
icon: React.PropTypes.string.isRequired,
|
||||
className: React.PropTypes.string,
|
||||
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>
|
||||
}
|
||||
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} />;
|
||||
}
|
||||
}
|
||||
|
||||
export class TruncatedText extends React.Component {
|
||||
static propTypes = {
|
||||
lines: React.PropTypes.number,
|
||||
}
|
||||
static propTypes = {
|
||||
lines: React.PropTypes.number
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
lines: null
|
||||
}
|
||||
static defaultProps = {
|
||||
lines: null
|
||||
};
|
||||
|
||||
render() {
|
||||
return <span className="truncated-text" style={{ WebkitLineClamp: this.props.lines }}>{this.props.children}</span>;
|
||||
}
|
||||
render() {
|
||||
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,
|
||||
}
|
||||
static propTypes = {
|
||||
message: React.PropTypes.string
|
||||
};
|
||||
|
||||
render() {
|
||||
return <span>{this.props.message} <span className="busy-indicator"></span></span>
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<span>{this.props.message} <span className="busy-indicator" /></span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class CurrencySymbol extends React.Component {
|
||||
render() {
|
||||
return <span>LBC</span>;
|
||||
}
|
||||
render() {
|
||||
return <span>LBC</span>;
|
||||
}
|
||||
}
|
||||
|
||||
export class CreditAmount extends React.Component {
|
||||
static propTypes = {
|
||||
amount: React.PropTypes.number.isRequired,
|
||||
precision: React.PropTypes.number,
|
||||
isEstimate: React.PropTypes.bool,
|
||||
label: React.PropTypes.bool,
|
||||
showFree: React.PropTypes.bool,
|
||||
look: React.PropTypes.oneOf(['indicator', 'plain']),
|
||||
}
|
||||
static propTypes = {
|
||||
amount: React.PropTypes.number.isRequired,
|
||||
precision: React.PropTypes.number,
|
||||
isEstimate: React.PropTypes.bool,
|
||||
label: React.PropTypes.bool,
|
||||
showFree: React.PropTypes.bool,
|
||||
look: React.PropTypes.oneOf(['indicator', 'plain'])
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
precision: 1,
|
||||
label: true,
|
||||
showFree: false,
|
||||
look: 'indicator',
|
||||
}
|
||||
static defaultProps = {
|
||||
precision: 1,
|
||||
label: true,
|
||||
showFree: false,
|
||||
look: 'indicator'
|
||||
};
|
||||
|
||||
render() {
|
||||
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'));
|
||||
} else {
|
||||
amountText = formattedAmount;
|
||||
}
|
||||
render() {
|
||||
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'));
|
||||
} else {
|
||||
amountText = formattedAmount;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={`credit-amount credit-amount--${this.props.look}`}>
|
||||
<span>
|
||||
{amountText}
|
||||
</span>
|
||||
{ this.props.isEstimate ? <span className="credit-amount__estimate" title={__("This is an estimate and does not include data fees")}>*</span> : null }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className={`credit-amount credit-amount--${this.props.look}`}>
|
||||
<span>
|
||||
{amountText}
|
||||
</span>
|
||||
{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,
|
||||
}
|
||||
static propTypes = {
|
||||
address: React.PropTypes.string
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._inputElem = null;
|
||||
}
|
||||
this._inputElem = null;
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Thumbnail extends React.Component {
|
||||
static propTypes = {
|
||||
src: React.PropTypes.string,
|
||||
}
|
||||
static propTypes = {
|
||||
src: React.PropTypes.string
|
||||
};
|
||||
|
||||
handleError() {
|
||||
if (this.state.imageUrl != this._defaultImageUri) {
|
||||
this.setState({
|
||||
imageUri: this._defaultImageUri,
|
||||
});
|
||||
}
|
||||
}
|
||||
handleError() {
|
||||
if (this.state.imageUrl != this._defaultImageUri) {
|
||||
this.setState({
|
||||
imageUri: this._defaultImageUri
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
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,
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
imageUri: this.props.src || this._defaultImageUri
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
setTimeout(() => {
|
||||
if (this._isMounted && !this.refs.img.complete) {
|
||||
this.setState({
|
||||
imageUri: this._defaultImageUri,
|
||||
});
|
||||
}
|
||||
}, this._maxLoadTime);
|
||||
}
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
setTimeout(() => {
|
||||
if (this._isMounted && !this.refs.img.complete) {
|
||||
this.setState({
|
||||
imageUri: this._defaultImageUri
|
||||
});
|
||||
}
|
||||
}, this._maxLoadTime);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const className = this.props.className ? this.props.className : '',
|
||||
otherProps = Object.assign({}, this.props)
|
||||
delete otherProps.className;
|
||||
return <img ref="img" onError={() => { this.handleError() }} {...otherProps} className={className} src={this.state.imageUri} />
|
||||
}
|
||||
render() {
|
||||
const className = this.props.className ? this.props.className : '',
|
||||
otherProps = Object.assign({}, this.props);
|
||||
delete otherProps.className;
|
||||
return (
|
||||
<img
|
||||
ref="img"
|
||||
onError={() => {
|
||||
this.handleError();
|
||||
}}
|
||||
{...otherProps}
|
||||
className={className}
|
||||
src={this.state.imageUri}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) => ({
|
||||
downloadProgress: selectDownloadProgress(state),
|
||||
downloadComplete: selectDownloadComplete(state),
|
||||
})
|
||||
const select = state => ({
|
||||
downloadProgress: selectDownloadProgress(state),
|
||||
downloadComplete: selectDownloadComplete(state)
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
startUpgrade: () => dispatch(doStartUpgrade()),
|
||||
cancelUpgrade: () => dispatch(doCancelUpgrade())
|
||||
})
|
||||
const perform = dispatch => ({
|
||||
startUpgrade: () => dispatch(doStartUpgrade()),
|
||||
cancelUpgrade: () => dispatch(doCancelUpgrade())
|
||||
});
|
||||
|
||||
export default connect(select, perform)(DownloadingModal)
|
||||
export default connect(select, perform)(DownloadingModal);
|
||||
|
|
|
@ -1,40 +1,62 @@
|
|||
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() {
|
||||
const {
|
||||
downloadProgress,
|
||||
downloadComplete,
|
||||
startUpgrade,
|
||||
cancelUpgrade,
|
||||
} = this.props
|
||||
render() {
|
||||
const {
|
||||
downloadProgress,
|
||||
downloadComplete,
|
||||
startUpgrade,
|
||||
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>
|
||||
<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>
|
||||
</div>
|
||||
) : null }
|
||||
<div className="modal__buttons">
|
||||
{downloadComplete
|
||||
? <Link button="primary" label={__("Begin Upgrade")} className="modal__button" onClick={startUpgrade} />
|
||||
: null}
|
||||
<Link button="alt" label={__("Cancel")} className="modal__button" onClick={cancelUpgrade} />
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<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>
|
||||
</div>
|
||||
: null}
|
||||
<div className="modal__buttons">
|
||||
{downloadComplete
|
||||
? <Link
|
||||
button="primary"
|
||||
label={__('Begin Upgrade')}
|
||||
className="modal__button"
|
||||
onClick={startUpgrade}
|
||||
/>
|
||||
: null}
|
||||
<Link
|
||||
button="alt"
|
||||
label={__('Cancel')}
|
||||
className="modal__button"
|
||||
onClick={cancelUpgrade}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DownloadingModal
|
||||
export default DownloadingModal;
|
||||
|
|
|
@ -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) => ({
|
||||
modal: selectCurrentModal(state),
|
||||
error: selectModalExtraContent(state),
|
||||
})
|
||||
const select = state => ({
|
||||
modal: selectCurrentModal(state),
|
||||
error: selectModalExtraContent(state)
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
closeModal: () => dispatch(doCloseModal())
|
||||
})
|
||||
const perform = dispatch => ({
|
||||
closeModal: () => dispatch(doCloseModal())
|
||||
});
|
||||
|
||||
export default connect(select, perform)(ErrorModal)
|
||||
export default connect(select, perform)(ErrorModal);
|
||||
|
|
|
@ -1,54 +1,63 @@
|
|||
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
|
||||
render() {
|
||||
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'),
|
||||
method: __('Method'),
|
||||
params: __('Parameters'),
|
||||
code: __('Error code'),
|
||||
message: __('Error message'),
|
||||
data: __('Error data'),
|
||||
}
|
||||
const error_key_labels = {
|
||||
connectionString: __('API connection string'),
|
||||
method: __('Method'),
|
||||
params: __('Parameters'),
|
||||
code: __('Error code'),
|
||||
message: __('Error message'),
|
||||
data: __('Error data')
|
||||
};
|
||||
|
||||
const errorInfoList = [];
|
||||
for (let key of Object.keys(error)) {
|
||||
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>
|
||||
);
|
||||
}
|
||||
const errorInfo = (
|
||||
<ul className="error-modal__error-list">{errorInfoList}</ul>
|
||||
);
|
||||
|
||||
const errorInfoList = []
|
||||
for (let key of Object.keys(error)) {
|
||||
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>);
|
||||
}
|
||||
const errorInfo = <ul className="error-modal__error-list">{errorInfoList}</ul>
|
||||
return (
|
||||
<ExpandableModal
|
||||
isOpen={modal == 'error'}
|
||||
contentLabel={__('Error')}
|
||||
className="error-modal"
|
||||
overlayClassName="error-modal-overlay"
|
||||
onConfirmed={closeModal}
|
||||
extraContent={errorInfo}
|
||||
>
|
||||
<h3 className="modal__header">{__('Error')}</h3>
|
||||
|
||||
return(
|
||||
<ExpandableModal
|
||||
isOpen={modal == 'error'}
|
||||
contentLabel={__("Error")} className="error-modal"
|
||||
overlayClassName="error-modal-overlay"
|
||||
onConfirmed={closeModal}
|
||||
extraContent={errorInfo}
|
||||
>
|
||||
<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>
|
||||
</ExpandableModal>
|
||||
)
|
||||
}
|
||||
<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>
|
||||
</ExpandableModal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorModal
|
||||
export default ErrorModal;
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
static propTypes = {
|
||||
type: React.PropTypes.oneOf(['file', 'directory']),
|
||||
initPath: React.PropTypes.string,
|
||||
onFileChosen: React.PropTypes.func
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
type: 'file',
|
||||
}
|
||||
static defaultProps = {
|
||||
type: 'file'
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
this.setState({
|
||||
path: this.props.initPath || null,
|
||||
});
|
||||
}
|
||||
componentWillMount() {
|
||||
this.setState({
|
||||
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
|
||||
return;
|
||||
}
|
||||
handleButtonClick() {
|
||||
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,
|
||||
});
|
||||
if (this.props.onFileChosen) {
|
||||
this.props.onFileChosen(path);
|
||||
}
|
||||
});
|
||||
}
|
||||
const path = paths[0];
|
||||
this.setState({
|
||||
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>
|
||||
{' '}
|
||||
<span className="file-selector__path">
|
||||
{this.state.path ?
|
||||
this.state.path :
|
||||
__('No File Chosen')}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
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>
|
||||
{' '}
|
||||
<span className="file-selector__path">
|
||||
{this.state.path ? this.state.path : __('No File Chosen')}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FileSelector;
|
||||
|
|
|
@ -1,75 +1,56 @@
|
|||
import React from 'react'
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { selectPlatform } from 'selectors/app';
|
||||
import {
|
||||
connect,
|
||||
} from 'react-redux'
|
||||
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';
|
||||
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'
|
||||
import {
|
||||
doOpenFileInShell,
|
||||
doOpenFileInFolder,
|
||||
doDeleteFile,
|
||||
} from 'actions/file_info'
|
||||
import {
|
||||
doPurchaseUri,
|
||||
doLoadVideo,
|
||||
} from 'actions/content'
|
||||
import FileActions from './view'
|
||||
doOpenFileInShell,
|
||||
doOpenFileInFolder,
|
||||
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),
|
||||
isAvailable: selectIsAvailableForUri(state, props),
|
||||
platform: selectPlatform(state),
|
||||
modal: selectCurrentModal(state),
|
||||
downloading: selectDownloadingForUri(state, props),
|
||||
costInfo: selectCostInfoForUri(state, props),
|
||||
loading: selectLoadingForUri(state, props),
|
||||
})
|
||||
const select = (state, props) => ({
|
||||
fileInfo: selectFileInfoForUri(state, props),
|
||||
isAvailable: selectIsAvailableForUri(state, props),
|
||||
platform: selectPlatform(state),
|
||||
modal: selectCurrentModal(state),
|
||||
downloading: selectDownloadingForUri(state, props),
|
||||
costInfo: selectCostInfoForUri(state, props),
|
||||
loading: selectLoadingForUri(state, props)
|
||||
});
|
||||
|
||||
return select
|
||||
}
|
||||
return select;
|
||||
};
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
checkAvailability: (uri) => dispatch(doFetchAvailability(uri)),
|
||||
closeModal: () => dispatch(doCloseModal()),
|
||||
openInFolder: (fileInfo) => dispatch(doOpenFileInFolder(fileInfo)),
|
||||
openInShell: (fileInfo) => dispatch(doOpenFileInShell(fileInfo)),
|
||||
deleteFile: (fileInfo, deleteFromComputer) => {
|
||||
dispatch(doHistoryBack())
|
||||
dispatch(doDeleteFile(fileInfo, deleteFromComputer))
|
||||
},
|
||||
openModal: (modal) => dispatch(doOpenModal(modal)),
|
||||
startDownload: (uri) => dispatch(doPurchaseUri(uri, 'affirmPurchase')),
|
||||
loadVideo: (uri) => dispatch(doLoadVideo(uri)),
|
||||
})
|
||||
const perform = dispatch => ({
|
||||
checkAvailability: uri => dispatch(doFetchAvailability(uri)),
|
||||
closeModal: () => dispatch(doCloseModal()),
|
||||
openInFolder: fileInfo => dispatch(doOpenFileInFolder(fileInfo)),
|
||||
openInShell: fileInfo => dispatch(doOpenFileInShell(fileInfo)),
|
||||
deleteFile: (fileInfo, deleteFromComputer) => {
|
||||
dispatch(doHistoryBack());
|
||||
dispatch(doDeleteFile(fileInfo, deleteFromComputer));
|
||||
},
|
||||
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);
|
||||
|
|
|
@ -1,151 +1,222 @@
|
|||
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)
|
||||
this.state = {
|
||||
forceShowActions: false,
|
||||
deleteChecked: false,
|
||||
}
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
forceShowActions: false,
|
||||
deleteChecked: false
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.checkAvailability(this.props.uri)
|
||||
}
|
||||
componentWillMount() {
|
||||
this.checkAvailability(this.props.uri);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.checkAvailability(nextProps.uri)
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.checkAvailability(nextProps.uri);
|
||||
}
|
||||
|
||||
checkAvailability(uri) {
|
||||
if (!this._uri || uri !== this._uri) {
|
||||
this._uri = uri;
|
||||
this.props.checkAvailability(uri)
|
||||
}
|
||||
}
|
||||
checkAvailability(uri) {
|
||||
if (!this._uri || uri !== this._uri) {
|
||||
this._uri = uri;
|
||||
this.props.checkAvailability(uri);
|
||||
}
|
||||
}
|
||||
|
||||
onShowFileActionsRowClicked() {
|
||||
this.setState({
|
||||
forceShowActions: true,
|
||||
});
|
||||
}
|
||||
onShowFileActionsRowClicked() {
|
||||
this.setState({
|
||||
forceShowActions: true
|
||||
});
|
||||
}
|
||||
|
||||
handleDeleteCheckboxClicked(event) {
|
||||
this.setState({
|
||||
deleteChecked: event.target.checked,
|
||||
})
|
||||
}
|
||||
handleDeleteCheckboxClicked(event) {
|
||||
this.setState({
|
||||
deleteChecked: event.target.checked
|
||||
});
|
||||
}
|
||||
|
||||
onAffirmPurchase() {
|
||||
this.props.closeModal()
|
||||
this.props.loadVideo(this.props.uri)
|
||||
}
|
||||
onAffirmPurchase() {
|
||||
this.props.closeModal();
|
||||
this.props.loadVideo(this.props.uri);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
fileInfo,
|
||||
isAvailable,
|
||||
platform,
|
||||
downloading,
|
||||
uri,
|
||||
deleteFile,
|
||||
openInFolder,
|
||||
openInShell,
|
||||
modal,
|
||||
openModal,
|
||||
closeModal,
|
||||
startDownload,
|
||||
costInfo,
|
||||
loading,
|
||||
} = this.props
|
||||
render() {
|
||||
const {
|
||||
fileInfo,
|
||||
isAvailable,
|
||||
platform,
|
||||
downloading,
|
||||
uri,
|
||||
deleteFile,
|
||||
openInFolder,
|
||||
openInShell,
|
||||
modal,
|
||||
openModal,
|
||||
closeModal,
|
||||
startDownload,
|
||||
costInfo,
|
||||
loading
|
||||
} = this.props;
|
||||
|
||||
const deleteChecked = this.state.deleteChecked,
|
||||
metadata = fileInfo ? fileInfo.metadata : null,
|
||||
openInFolderMessage = platform.startsWith('Mac') ? __('Open in Finder') : __('Open in Folder'),
|
||||
showMenu = fileInfo && Object.keys(fileInfo).length > 0,
|
||||
title = metadata ? metadata.title : uri;
|
||||
const deleteChecked = this.state.deleteChecked,
|
||||
metadata = fileInfo ? fileInfo.metadata : null,
|
||||
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) {
|
||||
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>
|
||||
{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')} />;
|
||||
} else {
|
||||
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)}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
console.log('handle this case of file action props?');
|
||||
}
|
||||
|
||||
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>
|
||||
{labelWithIcon}
|
||||
</div>
|
||||
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')}.
|
||||
</Modal>
|
||||
<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>
|
||||
<Modal
|
||||
isOpen={modal == 'confirmRemove'}
|
||||
contentLabel={__('Not enough credits')}
|
||||
type="confirm"
|
||||
confirmButtonLabel={__('Remove')}
|
||||
onConfirmed={() => deleteFile(fileInfo.outpoint, deleteChecked)}
|
||||
onAborted={closeModal}
|
||||
>
|
||||
<p>
|
||||
{__("Are you sure you'd like to remove")} <cite>{title}</cite>
|
||||
{' '}{__('from LBRY?')}
|
||||
</p>
|
||||
|
||||
} 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")} />
|
||||
} else {
|
||||
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)} />;
|
||||
} 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")}.
|
||||
</Modal>
|
||||
<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>
|
||||
<Modal isOpen={modal == 'confirmRemove'}
|
||||
contentLabel={__("Not enough credits")}
|
||||
type="confirm"
|
||||
confirmButtonLabel={__("Remove")}
|
||||
onConfirmed={() => deleteFile(fileInfo.outpoint, deleteChecked)}
|
||||
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>
|
||||
</Modal>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
<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;
|
||||
|
|
|
@ -1,50 +1,37 @@
|
|||
import React from 'react'
|
||||
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 {
|
||||
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'
|
||||
makeSelectClaimForUri,
|
||||
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),
|
||||
})
|
||||
const select = (state, props) => ({
|
||||
claim: selectClaimForUri(state, props),
|
||||
fileInfo: selectFileInfoForUri(state, props),
|
||||
obscureNsfw: selectObscureNsfw(state),
|
||||
metadata: selectMetadataForUri(state, props),
|
||||
isResolvingUri: selectResolvingUri(state, props)
|
||||
});
|
||||
|
||||
return select
|
||||
}
|
||||
return select;
|
||||
};
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||
resolveUri: (uri) => dispatch(doResolveUri(uri)),
|
||||
cancelResolveUri: (uri) => dispatch(doCancelResolveUri(uri))
|
||||
})
|
||||
const perform = dispatch => ({
|
||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||
cancelResolveUri: uri => dispatch(doCancelResolveUri(uri))
|
||||
});
|
||||
|
||||
export default connect(makeSelect, perform)(FileCard)
|
||||
export default connect(makeSelect, perform)(FileCard);
|
||||
|
|
|
@ -2,111 +2,121 @@ 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)
|
||||
}
|
||||
componentWillMount() {
|
||||
this.resolve(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.resolve(nextProps)
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.resolve(nextProps);
|
||||
}
|
||||
|
||||
resolve(props) {
|
||||
const {
|
||||
isResolvingUri,
|
||||
resolveUri,
|
||||
claim,
|
||||
uri,
|
||||
} = props
|
||||
resolve(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
|
||||
componentWillUnmount() {
|
||||
const { isResolvingUri, cancelResolveUri, uri } = this.props;
|
||||
|
||||
if (isResolvingUri) {
|
||||
cancelResolveUri(uri)
|
||||
}
|
||||
}
|
||||
if (isResolvingUri) {
|
||||
cancelResolveUri(uri);
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseOver() {
|
||||
this.setState({
|
||||
hovered: true,
|
||||
});
|
||||
}
|
||||
handleMouseOver() {
|
||||
this.setState({
|
||||
hovered: true
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseOut() {
|
||||
this.setState({
|
||||
hovered: false,
|
||||
});
|
||||
}
|
||||
handleMouseOut() {
|
||||
this.setState({
|
||||
hovered: false
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
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 obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
||||
|
||||
const uri = lbryuri.normalize(this.props.uri);
|
||||
const title = !isResolvingUri && metadata && metadata.title ? metadata.title : uri;
|
||||
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
||||
let description = '';
|
||||
if (isResolvingUri) {
|
||||
description = __('Loading...');
|
||||
} else if (metadata && metadata.description) {
|
||||
description = metadata.description;
|
||||
} else if (claim === null) {
|
||||
description = __('This address contains no content.');
|
||||
}
|
||||
|
||||
let description = ""
|
||||
if (isResolvingUri) {
|
||||
description = __("Loading...")
|
||||
} else if (metadata && metadata.description) {
|
||||
description = metadata.description
|
||||
} else if (claim === null) {
|
||||
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)}>
|
||||
<div className="card__inner">
|
||||
<Link onClick={() => navigate('/show', { uri })} className="card__link">
|
||||
<div className="card__title-identity">
|
||||
<h5 title={title}><TruncatedText lines={1}>{title}</TruncatedText></h5>
|
||||
<div className="card__subtitle">
|
||||
<span style={{float: "right"}}>
|
||||
<FilePrice uri={uri} />
|
||||
{ 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>
|
||||
}
|
||||
<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'>
|
||||
<p>
|
||||
{__("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}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<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"
|
||||
>
|
||||
<div className="card__title-identity">
|
||||
<h5 title={title}>
|
||||
<TruncatedText lines={1}>{title}</TruncatedText>
|
||||
</h5>
|
||||
<div className="card__subtitle">
|
||||
<span style={{ float: 'right' }}>
|
||||
<FilePrice uri={uri} />
|
||||
{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 className="card__content card__subtext card__subtext--two-lines">
|
||||
<TruncatedText lines={2}>{description}</TruncatedText>
|
||||
</div>
|
||||
</Link>
|
||||
{obscureNsfw && this.state.hovered
|
||||
? <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')}
|
||||
/>.
|
||||
</p>
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FileCard
|
||||
export default FileCard;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -2,92 +2,99 @@ 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)
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
sortBy: 'date',
|
||||
}
|
||||
this.state = {
|
||||
sortBy: 'date'
|
||||
};
|
||||
|
||||
this._sortFunctions = {
|
||||
date: function(fileInfos) {
|
||||
return fileInfos.slice().reverse();
|
||||
},
|
||||
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;
|
||||
if (title1 < title2) {
|
||||
return -1;
|
||||
} else if (title1 > title2) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
})
|
||||
},
|
||||
filename: function(fileInfos) {
|
||||
return fileInfos.slice().sort(function({file_name: fileName1}, {file_name: fileName2}) {
|
||||
const fileName1Lower = fileName1.toLowerCase();
|
||||
const fileName2Lower = fileName2.toLowerCase();
|
||||
if (fileName1Lower < fileName2Lower) {
|
||||
return -1;
|
||||
} else if (fileName2Lower > fileName1Lower) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
this._sortFunctions = {
|
||||
date: function(fileInfos) {
|
||||
return fileInfos.slice().reverse();
|
||||
},
|
||||
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;
|
||||
if (title1 < title2) {
|
||||
return -1;
|
||||
} else if (title1 > title2) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
},
|
||||
filename: function(fileInfos) {
|
||||
return fileInfos
|
||||
.slice()
|
||||
.sort(function({ file_name: fileName1 }, { file_name: fileName2 }) {
|
||||
const fileName1Lower = fileName1.toLowerCase();
|
||||
const fileName2Lower = fileName2.toLowerCase();
|
||||
if (fileName1Lower < fileName2Lower) {
|
||||
return -1;
|
||||
} else if (fileName2Lower > fileName1Lower) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
handleSortChanged(event) {
|
||||
this.setState({
|
||||
sortBy: event.target.value,
|
||||
})
|
||||
}
|
||||
handleSortChanged(event) {
|
||||
this.setState({
|
||||
sortBy: event.target.value
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
handleSortChanged,
|
||||
fetching,
|
||||
fileInfos,
|
||||
} = this.props
|
||||
const {
|
||||
sortBy,
|
||||
} = this.state
|
||||
const content = []
|
||||
render() {
|
||||
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} />)
|
||||
})
|
||||
return (
|
||||
<section className="file-list__header">
|
||||
{ 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>
|
||||
</FormField>
|
||||
</span>
|
||||
{content}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
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}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<section className="file-list__header">
|
||||
{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>
|
||||
</FormField>
|
||||
</span>
|
||||
{content}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FileList
|
||||
export default FileList;
|
||||
|
|
|
@ -1,29 +1,23 @@
|
|||
import React from 'react'
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { doSearch } from 'actions/search';
|
||||
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'
|
||||
selectIsSearching,
|
||||
selectCurrentSearchResults,
|
||||
selectSearchQuery
|
||||
} from 'selectors/search';
|
||||
import { doNavigate } from 'actions/app';
|
||||
import FileListSearch from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
isSearching: selectIsSearching(state),
|
||||
query: selectSearchQuery(state),
|
||||
results: selectCurrentSearchResults(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);
|
||||
|
|
|
@ -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>
|
||||
<span className="empty">
|
||||
{__("No one has checked anything in for %s yet."), query} { ' ' }
|
||||
<Link label={__("Be the first")} onClick={() => navigate('/publish')} />
|
||||
</span>
|
||||
</section>;
|
||||
return (
|
||||
<section>
|
||||
<span className="empty">
|
||||
{(__('No one has checked anything in for %s yet.'), query)} {' '}
|
||||
<Link label={__('Be the first')} onClick={() => navigate('/publish')} />
|
||||
</span>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
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) {
|
||||
const uri = lbryuri.build({
|
||||
channelName: channel_name,
|
||||
contentName: name,
|
||||
claimId: channel_id || claim_id
|
||||
});
|
||||
|
||||
rows.push(<FileTile key={uri} uri={uri} />);
|
||||
}
|
||||
return <div>{rows}</div>;
|
||||
};
|
||||
|
||||
class FileListSearch extends React.Component {
|
||||
componentWillMount() {
|
||||
this.props.search(this.props.query);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isSearching, results } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isSearching &&
|
||||
!results &&
|
||||
<BusyMessage message={__('Looking up the Dewey Decimals')} />}
|
||||
|
||||
{isSearching &&
|
||||
results &&
|
||||
<BusyMessage message={__('Refreshing the Dewey Decimals')} />}
|
||||
|
||||
{results && !!results.length
|
||||
? <FileListSearchResults {...this.props} />
|
||||
: <SearchNoResults {...this.props} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
const uri = lbryuri.build({
|
||||
channelName: channel_name,
|
||||
contentName: name,
|
||||
claimId: channel_id || claim_id,
|
||||
});
|
||||
|
||||
rows.push(
|
||||
<FileTile key={uri} uri={uri} />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>{rows}</div>
|
||||
);
|
||||
}
|
||||
|
||||
class FileListSearch extends React.Component{
|
||||
componentWillMount() {
|
||||
this.props.search(this.props.query)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
isSearching,
|
||||
results
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isSearching && !results &&
|
||||
<BusyMessage message={__("Looking up the Dewey Decimals")} />}
|
||||
|
||||
{isSearching && results &&
|
||||
<BusyMessage message={__("Refreshing the Dewey Decimals")} />}
|
||||
|
||||
{(results && !!results.length) ?
|
||||
<FileListSearchResults {...this.props} /> :
|
||||
<SearchNoResults {...this.props} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default FileListSearch
|
||||
export default FileListSearch;
|
||||
|
|
|
@ -1,31 +1,27 @@
|
|||
import React from 'react'
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { doFetchCostInfoForUri } from 'actions/cost_info';
|
||||
import {
|
||||
connect,
|
||||
} from 'react-redux'
|
||||
import {
|
||||
doFetchCostInfoForUri,
|
||||
} from 'actions/cost_info'
|
||||
import {
|
||||
makeSelectCostInfoForUri,
|
||||
makeSelectFetchingCostInfoForUri,
|
||||
} from 'selectors/cost_info'
|
||||
import FilePrice from './view'
|
||||
makeSelectCostInfoForUri,
|
||||
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),
|
||||
})
|
||||
const select = (state, props) => ({
|
||||
costInfo: selectCostInfoForUri(state, props),
|
||||
fetching: selectFetchingCostInfoForUri(state, props)
|
||||
});
|
||||
|
||||
return select
|
||||
}
|
||||
return select;
|
||||
};
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
fetchCostInfo: (uri) => dispatch(doFetchCostInfoForUri(uri)),
|
||||
// cancelFetchCostInfo: (uri) => dispatch(doCancelFetchCostInfoForUri(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);
|
||||
|
|
|
@ -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{
|
||||
componentWillMount() {
|
||||
this.fetchCost(this.props)
|
||||
}
|
||||
class FilePrice extends React.Component {
|
||||
componentWillMount() {
|
||||
this.fetchCost(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.fetchCost(nextProps)
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.fetchCost(nextProps);
|
||||
}
|
||||
|
||||
fetchCost(props) {
|
||||
const {
|
||||
costInfo,
|
||||
fetchCostInfo,
|
||||
uri,
|
||||
fetching,
|
||||
} = props
|
||||
fetchCost(props) {
|
||||
const { costInfo, fetchCostInfo, uri, fetching } = props;
|
||||
|
||||
if (costInfo === undefined && !fetching) {
|
||||
fetchCostInfo(uri)
|
||||
}
|
||||
}
|
||||
if (costInfo === undefined && !fetching) {
|
||||
fetchCostInfo(uri);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
costInfo,
|
||||
look = 'indicator',
|
||||
} = this.props
|
||||
render() {
|
||||
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>;
|
||||
}
|
||||
if (!costInfo) {
|
||||
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;
|
||||
|
|
|
@ -1,48 +1,36 @@
|
|||
import React from 'react'
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { doNavigate } from 'actions/app';
|
||||
import { doResolveUri } from 'actions/content';
|
||||
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'
|
||||
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';
|
||||
|
||||
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),
|
||||
})
|
||||
const select = (state, props) => ({
|
||||
claim: selectClaimForUri(state, props),
|
||||
fileInfo: selectFileInfoForUri(state, props),
|
||||
obscureNsfw: selectObscureNsfw(state),
|
||||
metadata: selectMetadataForUri(state, props),
|
||||
isResolvingUri: selectResolvingUri(state, props)
|
||||
});
|
||||
|
||||
return select
|
||||
}
|
||||
return select;
|
||||
};
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||
resolveUri: (uri) => dispatch(doResolveUri(uri)),
|
||||
})
|
||||
const perform = dispatch => ({
|
||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||
resolveUri: uri => dispatch(doResolveUri(uri))
|
||||
});
|
||||
|
||||
export default connect(makeSelect, perform)(FileTile)
|
||||
export default connect(makeSelect, perform)(FileTile);
|
||||
|
|
|
@ -3,113 +3,140 @@ 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)
|
||||
this.state = {
|
||||
showNsfwHelp: false,
|
||||
}
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showNsfwHelp: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
isResolvingUri,
|
||||
resolveUri,
|
||||
claim,
|
||||
uri,
|
||||
} = this.props
|
||||
componentDidMount() {
|
||||
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) {
|
||||
this.setState({
|
||||
showNsfwHelp: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
handleMouseOver() {
|
||||
if (
|
||||
this.props.obscureNsfw &&
|
||||
this.props.metadata &&
|
||||
this.props.metadata.nsfw
|
||||
) {
|
||||
this.setState({
|
||||
showNsfwHelp: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseOut() {
|
||||
if (this.state.showNsfwHelp) {
|
||||
this.setState({
|
||||
showNsfwHelp: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
handleMouseOut() {
|
||||
if (this.state.showNsfwHelp) {
|
||||
this.setState({
|
||||
showNsfwHelp: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
claim,
|
||||
metadata,
|
||||
isResolvingUri,
|
||||
showEmpty,
|
||||
navigate,
|
||||
hidePrice,
|
||||
} = this.props
|
||||
render() {
|
||||
const {
|
||||
claim,
|
||||
metadata,
|
||||
isResolvingUri,
|
||||
showEmpty,
|
||||
navigate,
|
||||
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 obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
||||
let onClick = () => navigate('/show', { uri })
|
||||
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 obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
||||
let onClick = () => navigate('/show', { uri });
|
||||
|
||||
let description = ""
|
||||
if (isClaimed) {
|
||||
description = metadata && metadata.description
|
||||
} else if (isResolvingUri) {
|
||||
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> }
|
||||
</span>
|
||||
} else if (showEmpty === FileTile.SHOW_EMPTY_PENDING) {
|
||||
description = <span className="empty">{__("This file is pending confirmation.")}</span>
|
||||
}
|
||||
let description = '';
|
||||
if (isClaimed) {
|
||||
description = metadata && metadata.description;
|
||||
} else if (isResolvingUri) {
|
||||
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>}
|
||||
</span>
|
||||
);
|
||||
} else if (showEmpty === FileTile.SHOW_EMPTY_PENDING) {
|
||||
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)}>
|
||||
<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="file-tile__content">
|
||||
<div className="card__title-primary">
|
||||
{ !hidePrice ? <FilePrice uri={this.props.uri} /> : null}
|
||||
<div className="meta">{uri}</div>
|
||||
<h3><TruncatedText lines={1}>{title}</TruncatedText></h3>
|
||||
</div>
|
||||
<div className="card__content card__subtext">
|
||||
<TruncatedText lines={3}>
|
||||
{description}
|
||||
</TruncatedText>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
{this.state.showNsfwHelp
|
||||
? <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")} />.
|
||||
</p>
|
||||
</div>
|
||||
: null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<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 className="file-tile__content">
|
||||
<div className="card__title-primary">
|
||||
{!hidePrice ? <FilePrice uri={this.props.uri} /> : null}
|
||||
<div className="meta">{uri}</div>
|
||||
<h3><TruncatedText lines={1}>{title}</TruncatedText></h3>
|
||||
</div>
|
||||
<div className="card__content card__subtext">
|
||||
<TruncatedText lines={3}>
|
||||
{description}
|
||||
</TruncatedText>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
{this.state.showNsfwHelp
|
||||
? <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')}
|
||||
/>.
|
||||
</p>
|
||||
</div>
|
||||
: null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FileTile
|
||||
export default FileTile;
|
||||
|
|
|
@ -1,199 +1,259 @@
|
|||
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'];
|
||||
formFieldFileSelectorTypes = ['file', 'directory'],
|
||||
formFieldNestedLabelTypes = ['radio', 'checkbox'];
|
||||
|
||||
function formFieldId() {
|
||||
return "form-field-" + (++formFieldCounter);
|
||||
return 'form-field-' + ++formFieldCounter;
|
||||
}
|
||||
|
||||
export class FormField extends React.Component {
|
||||
static propTypes = {
|
||||
type: React.PropTypes.string.isRequired,
|
||||
prefix: React.PropTypes.string,
|
||||
postfix: React.PropTypes.string,
|
||||
hasError: React.PropTypes.bool
|
||||
}
|
||||
static propTypes = {
|
||||
type: React.PropTypes.string.isRequired,
|
||||
prefix: React.PropTypes.string,
|
||||
postfix: React.PropTypes.string,
|
||||
hasError: React.PropTypes.bool
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._fieldRequiredText = __('This field is required');
|
||||
this._type = null;
|
||||
this._element = null;
|
||||
this._fieldRequiredText = __('This field is required');
|
||||
this._type = null;
|
||||
this._element = null;
|
||||
|
||||
this.state = {
|
||||
isError: null,
|
||||
errorMessage: null,
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
isError: null,
|
||||
errorMessage: null
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
if (['text', 'number', 'radio', 'checkbox'].includes(this.props.type)) {
|
||||
this._element = 'input';
|
||||
this._type = this.props.type;
|
||||
} else if (this.props.type == 'text-number') {
|
||||
this._element = 'input';
|
||||
this._type = 'text';
|
||||
} else if (formFieldFileSelectorTypes.includes(this.props.type)) {
|
||||
this._element = 'input';
|
||||
this._type = 'hidden';
|
||||
} else {
|
||||
// Non <input> field, e.g. <select>, <textarea>
|
||||
this._element = this.props.type;
|
||||
}
|
||||
}
|
||||
componentWillMount() {
|
||||
if (['text', 'number', 'radio', 'checkbox'].includes(this.props.type)) {
|
||||
this._element = 'input';
|
||||
this._type = this.props.type;
|
||||
} else if (this.props.type == 'text-number') {
|
||||
this._element = 'input';
|
||||
this._type = 'text';
|
||||
} else if (formFieldFileSelectorTypes.includes(this.props.type)) {
|
||||
this._element = 'input';
|
||||
this._type = 'hidden';
|
||||
} else {
|
||||
// Non <input> field, e.g. <select>, <textarea>
|
||||
this._element = this.props.type;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
/**
|
||||
componentDidMount() {
|
||||
/**
|
||||
* We have to add the webkitdirectory attribute here because React doesn't allow it in JSX
|
||||
* https://github.com/facebook/react/issues/3468
|
||||
*/
|
||||
if (this.props.type == 'directory') {
|
||||
this.refs.field.webkitdirectory = true;
|
||||
}
|
||||
}
|
||||
if (this.props.type == 'directory') {
|
||||
this.refs.field.webkitdirectory = true;
|
||||
}
|
||||
}
|
||||
|
||||
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})
|
||||
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);
|
||||
}
|
||||
}
|
||||
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 });
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
showError(text) {
|
||||
this.setState({
|
||||
isError: true,
|
||||
errorMessage: text,
|
||||
});
|
||||
}
|
||||
showError(text) {
|
||||
this.setState({
|
||||
isError: true,
|
||||
errorMessage: text
|
||||
});
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.refs.field.focus();
|
||||
}
|
||||
focus() {
|
||||
this.refs.field.focus();
|
||||
}
|
||||
|
||||
getValue() {
|
||||
if (this.props.type == 'checkbox') {
|
||||
return this.refs.field.checked;
|
||||
} else {
|
||||
return this.refs.field.value;
|
||||
}
|
||||
}
|
||||
getValue() {
|
||||
if (this.props.type == 'checkbox') {
|
||||
return this.refs.field.checked;
|
||||
} else {
|
||||
return this.refs.field.value;
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedElement() {
|
||||
return this.refs.field.options[this.refs.field.selectedIndex];
|
||||
}
|
||||
getSelectedElement() {
|
||||
return this.refs.field.options[this.refs.field.selectedIndex];
|
||||
}
|
||||
|
||||
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,
|
||||
elementId = this.props.id ? this.props.id : formFieldId(),
|
||||
renderElementInsideLabel = this.props.label && formFieldNestedLabelTypes.includes(this.props.type);
|
||||
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,
|
||||
elementId = this.props.id ? this.props.id : formFieldId(),
|
||||
renderElementInsideLabel =
|
||||
this.props.label && formFieldNestedLabelTypes.includes(this.props.type);
|
||||
|
||||
delete otherProps.type;
|
||||
delete otherProps.label;
|
||||
delete otherProps.hasError;
|
||||
delete otherProps.className;
|
||||
delete otherProps.postfix;
|
||||
delete otherProps.prefix;
|
||||
delete otherProps.type;
|
||||
delete otherProps.label;
|
||||
delete otherProps.hasError;
|
||||
delete otherProps.className;
|
||||
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}>
|
||||
{this.props.children}
|
||||
</this._element>;
|
||||
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>
|
||||
);
|
||||
|
||||
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> : '' }
|
||||
</div>
|
||||
}
|
||||
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>
|
||||
: ''}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class FormRow extends React.Component {
|
||||
static propTypes = {
|
||||
label: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.element]),
|
||||
// helper: React.PropTypes.html,
|
||||
}
|
||||
static propTypes = {
|
||||
label: React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.element
|
||||
])
|
||||
// helper: React.PropTypes.html,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._fieldRequiredText = __('This field is required');
|
||||
this._fieldRequiredText = __('This field is required');
|
||||
|
||||
this.state = {
|
||||
isError: false,
|
||||
errorMessage: null,
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
isError: false,
|
||||
errorMessage: null
|
||||
};
|
||||
}
|
||||
|
||||
showError(text) {
|
||||
this.setState({
|
||||
isError: true,
|
||||
errorMessage: text,
|
||||
});
|
||||
}
|
||||
showError(text) {
|
||||
this.setState({
|
||||
isError: true,
|
||||
errorMessage: text
|
||||
});
|
||||
}
|
||||
|
||||
showRequiredError() {
|
||||
this.showError(this._fieldRequiredText);
|
||||
}
|
||||
showRequiredError() {
|
||||
this.showError(this._fieldRequiredText);
|
||||
}
|
||||
|
||||
clearError(text) {
|
||||
this.setState({
|
||||
isError: false,
|
||||
errorMessage: ''
|
||||
});
|
||||
}
|
||||
clearError(text) {
|
||||
this.setState({
|
||||
isError: false,
|
||||
errorMessage: ''
|
||||
});
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.refs.field.getValue();
|
||||
}
|
||||
getValue() {
|
||||
return this.refs.field.getValue();
|
||||
}
|
||||
|
||||
getSelectedElement() {
|
||||
return this.refs.field.getSelectedElement();
|
||||
}
|
||||
getSelectedElement() {
|
||||
return this.refs.field.getSelectedElement();
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.refs.field.focus();
|
||||
}
|
||||
focus() {
|
||||
this.refs.field.focus();
|
||||
}
|
||||
|
||||
render() {
|
||||
const fieldProps = Object.assign({}, this.props),
|
||||
elementId = formFieldId(),
|
||||
renderLabelInFormField = formFieldNestedLabelTypes.includes(this.props.type);
|
||||
render() {
|
||||
const fieldProps = Object.assign({}, this.props),
|
||||
elementId = formFieldId(),
|
||||
renderLabelInFormField = formFieldNestedLabelTypes.includes(
|
||||
this.props.type
|
||||
);
|
||||
|
||||
if (!renderLabelInFormField) {
|
||||
delete fieldProps.label;
|
||||
}
|
||||
delete fieldProps.helper;
|
||||
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' : '')}>
|
||||
{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>
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) => ({
|
||||
balance: lbry.formatCredits(selectBalance(state), 1),
|
||||
publish: __("Publish"),
|
||||
})
|
||||
const select = state => ({
|
||||
balance: lbry.formatCredits(selectBalance(state), 1),
|
||||
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);
|
||||
|
|
|
@ -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">
|
||||
<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" />
|
||||
</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>
|
||||
</div>
|
||||
<div className="header__item">
|
||||
<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" />
|
||||
</div>
|
||||
<div className="header__item">
|
||||
<Link onClick={() => navigate('/settings')} button="alt button--flat" icon="icon-gear" />
|
||||
</div>
|
||||
</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"
|
||||
/>
|
||||
</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}
|
||||
/>
|
||||
</div>
|
||||
<div className="header__item">
|
||||
<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"
|
||||
/>
|
||||
</div>
|
||||
<div className="header__item">
|
||||
<Link
|
||||
onClick={() => navigate('/settings')}
|
||||
button="alt button--flat"
|
||||
icon="icon-gear"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,47 +1,53 @@
|
|||
import React from 'react';
|
||||
import {Icon} from 'component/common.js';
|
||||
import { Icon } from 'component/common.js';
|
||||
|
||||
const Link = (props) => {
|
||||
const {
|
||||
href,
|
||||
title,
|
||||
onClick,
|
||||
style,
|
||||
label,
|
||||
icon,
|
||||
badge,
|
||||
button,
|
||||
hidden,
|
||||
disabled,
|
||||
children,
|
||||
} = props
|
||||
const Link = props => {
|
||||
const {
|
||||
href,
|
||||
title,
|
||||
onClick,
|
||||
style,
|
||||
label,
|
||||
icon,
|
||||
badge,
|
||||
button,
|
||||
hidden,
|
||||
disabled,
|
||||
children
|
||||
} = props;
|
||||
|
||||
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.disabled ? ' disabled' : '');
|
||||
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.disabled ? ' disabled' : '');
|
||||
|
||||
let content;
|
||||
if (children) {
|
||||
content = children;
|
||||
} else {
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
let content;
|
||||
if (children) {
|
||||
content = children
|
||||
} else {
|
||||
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}
|
||||
onClick={onClick}
|
||||
{...('style' in props ? { style: style } : {})}
|
||||
>
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<a className={className} href={href || 'javascript:;'} title={title}
|
||||
onClick={onClick}
|
||||
{... 'style' in props ? {style: style} : {}}>
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export default Link
|
||||
export default Link;
|
||||
|
|
|
@ -1,46 +1,54 @@
|
|||
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,
|
||||
}
|
||||
static propTypes = {
|
||||
message: React.PropTypes.string.isRequired,
|
||||
details: React.PropTypes.string,
|
||||
isWarning: React.PropTypes.bool
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
message: null,
|
||||
details: null,
|
||||
isLagging: false,
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
message: null,
|
||||
details: null,
|
||||
isLagging: false
|
||||
};
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
isWarning: false,
|
||||
}
|
||||
static defaultProps = {
|
||||
isWarning: false
|
||||
};
|
||||
|
||||
render() {
|
||||
const imgSrc = lbry.imagePath('lbry-white-485x160.png');
|
||||
return (
|
||||
<div className="load-screen">
|
||||
<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> }
|
||||
</h3>
|
||||
<span className={'load-screen__details ' + (this.props.isWarning ? 'load-screen__details--warning' : '')}>{this.props.details}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
const imgSrc = lbry.imagePath('lbry-white-485x160.png');
|
||||
return (
|
||||
<div className="load-screen">
|
||||
<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>}
|
||||
</h3>
|
||||
<span
|
||||
className={
|
||||
'load-screen__details ' +
|
||||
(this.props.isWarning ? 'load-screen__details--warning' : '')
|
||||
}
|
||||
>
|
||||
{this.props.details}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default LoadScreen;
|
||||
|
|
|
@ -1,93 +1,112 @@
|
|||
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 {
|
||||
static propTypes = {
|
||||
href: React.PropTypes.string,
|
||||
label: React.PropTypes.string,
|
||||
icon: React.PropTypes.string,
|
||||
onClick: React.PropTypes.func,
|
||||
}
|
||||
static propTypes = {
|
||||
href: React.PropTypes.string,
|
||||
label: React.PropTypes.string,
|
||||
icon: React.PropTypes.string,
|
||||
onClick: React.PropTypes.func
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
iconPosition: 'left',
|
||||
}
|
||||
static defaultProps = {
|
||||
iconPosition: 'left'
|
||||
};
|
||||
|
||||
render() {
|
||||
var icon = (this.props.icon ? <Icon icon={this.props.icon} fixed /> : null);
|
||||
render() {
|
||||
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}>
|
||||
{this.props.iconPosition == 'left' ? icon : null}
|
||||
{this.props.label}
|
||||
{this.props.iconPosition == 'left' ? null : icon}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<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}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class DropDownMenu extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._isWindowClickBound = false;
|
||||
this._menuDiv = null;
|
||||
this._isWindowClickBound = false;
|
||||
this._menuDiv = null;
|
||||
|
||||
this.state = {
|
||||
menuOpen: false,
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
menuOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._isWindowClickBound) {
|
||||
window.removeEventListener('click', this.handleWindowClick, false);
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
if (this._isWindowClickBound) {
|
||||
window.removeEventListener('click', this.handleWindowClick, false);
|
||||
}
|
||||
}
|
||||
|
||||
handleMenuIconClick(e) {
|
||||
this.setState({
|
||||
menuOpen: !this.state.menuOpen,
|
||||
});
|
||||
if (!this.state.menuOpen && !this._isWindowClickBound) {
|
||||
this._isWindowClickBound = true;
|
||||
window.addEventListener('click', this.handleWindowClick, false);
|
||||
e.stopPropagation();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
handleMenuIconClick(e) {
|
||||
this.setState({
|
||||
menuOpen: !this.state.menuOpen
|
||||
});
|
||||
if (!this.state.menuOpen && !this._isWindowClickBound) {
|
||||
this._isWindowClickBound = true;
|
||||
window.addEventListener('click', this.handleWindowClick, false);
|
||||
e.stopPropagation();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
handleMenuClick(e) {
|
||||
// Event bubbles up to the menu after a link is clicked
|
||||
this.setState({
|
||||
menuOpen: false,
|
||||
});
|
||||
}
|
||||
handleMenuClick(e) {
|
||||
// Event bubbles up to the menu after a link is clicked
|
||||
this.setState({
|
||||
menuOpen: false
|
||||
});
|
||||
}
|
||||
|
||||
handleWindowClick(e) {
|
||||
if (this.state.menuOpen &&
|
||||
(!this._menuDiv || !this._menuDiv.contains(e.target))) {
|
||||
this.setState({
|
||||
menuOpen: false
|
||||
});
|
||||
}
|
||||
}
|
||||
handleWindowClick(e) {
|
||||
if (
|
||||
this.state.menuOpen &&
|
||||
(!this._menuDiv || !this._menuDiv.contains(e.target))
|
||||
) {
|
||||
this.setState({
|
||||
menuOpen: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.menuOpen && this._isWindowClickBound) {
|
||||
this._isWindowClickBound = false;
|
||||
window.removeEventListener('click', this.handleWindowClick, false);
|
||||
}
|
||||
return (
|
||||
<div className="menu-container">
|
||||
<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) }}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
render() {
|
||||
if (!this.state.menuOpen && this._isWindowClickBound) {
|
||||
this._isWindowClickBound = false;
|
||||
window.removeEventListener('click', this.handleWindowClick, false);
|
||||
}
|
||||
return (
|
||||
<div className="menu-container">
|
||||
<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);
|
||||
}}
|
||||
>
|
||||
{this.props.children}
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,17 +2,20 @@ import React from 'react';
|
|||
import ReactModal from 'react-modal';
|
||||
|
||||
export class ModalPage extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ReactModal onCloseRequested={this.props.onAborted || this.props.onConfirmed} {...this.props}
|
||||
className={(this.props.className || '') + ' modal-page'}
|
||||
overlayClassName="modal-overlay">
|
||||
<div className="modal-page__content">
|
||||
{this.props.children}
|
||||
</div>
|
||||
</ReactModal>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<ReactModal
|
||||
onCloseRequested={this.props.onAborted || this.props.onConfirmed}
|
||||
{...this.props}
|
||||
className={(this.props.className || '') + ' modal-page'}
|
||||
overlayClassName="modal-overlay"
|
||||
>
|
||||
<div className="modal-page__content">
|
||||
{this.props.children}
|
||||
</div>
|
||||
</ReactModal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ModalPage
|
||||
export default ModalPage;
|
||||
|
|
|
@ -1,91 +1,123 @@
|
|||
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 = {
|
||||
type: React.PropTypes.oneOf(['alert', 'confirm', 'custom']),
|
||||
overlay: React.PropTypes.bool,
|
||||
onConfirmed: React.PropTypes.func,
|
||||
onAborted: React.PropTypes.func,
|
||||
confirmButtonLabel: React.PropTypes.string,
|
||||
abortButtonLabel: React.PropTypes.string,
|
||||
confirmButtonDisabled: React.PropTypes.bool,
|
||||
abortButtonDisabled: React.PropTypes.bool,
|
||||
}
|
||||
static propTypes = {
|
||||
type: React.PropTypes.oneOf(['alert', 'confirm', 'custom']),
|
||||
overlay: React.PropTypes.bool,
|
||||
onConfirmed: React.PropTypes.func,
|
||||
onAborted: React.PropTypes.func,
|
||||
confirmButtonLabel: React.PropTypes.string,
|
||||
abortButtonLabel: React.PropTypes.string,
|
||||
confirmButtonDisabled: React.PropTypes.bool,
|
||||
abortButtonDisabled: React.PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
type: 'alert',
|
||||
overlay: true,
|
||||
confirmButtonLabel: app.i18n.__('OK'),
|
||||
abortButtonLabel: app.i18n.__('Cancel'),
|
||||
confirmButtonDisabled: false,
|
||||
abortButtonDisabled: false,
|
||||
}
|
||||
static defaultProps = {
|
||||
type: 'alert',
|
||||
overlay: true,
|
||||
confirmButtonLabel: app.i18n.__('OK'),
|
||||
abortButtonLabel: app.i18n.__('Cancel'),
|
||||
confirmButtonDisabled: false,
|
||||
abortButtonDisabled: false
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<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'}>
|
||||
<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} />
|
||||
{this.props.type == 'confirm'
|
||||
? <Link button="alt" label={this.props.abortButtonLabel} className="modal__button" disabled={this.props.abortButtonDisabled} onClick={this.props.onAborted} />
|
||||
: null}
|
||||
</div>}
|
||||
</ReactModal>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<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'
|
||||
}
|
||||
>
|
||||
<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}
|
||||
/>
|
||||
{this.props.type == 'confirm'
|
||||
? <Link
|
||||
button="alt"
|
||||
label={this.props.abortButtonLabel}
|
||||
className="modal__button"
|
||||
disabled={this.props.abortButtonDisabled}
|
||||
onClick={this.props.onAborted}
|
||||
/>
|
||||
: null}
|
||||
</div>}
|
||||
</ReactModal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExpandableModal extends React.Component {
|
||||
static propTypes = {
|
||||
expandButtonLabel: React.PropTypes.string,
|
||||
extraContent: React.PropTypes.element,
|
||||
}
|
||||
static propTypes = {
|
||||
expandButtonLabel: React.PropTypes.string,
|
||||
extraContent: React.PropTypes.element
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
confirmButtonLabel: app.i18n.__('OK'),
|
||||
expandButtonLabel: app.i18n.__('Show More...'),
|
||||
hideButtonLabel: app.i18n.__('Show Less'),
|
||||
}
|
||||
static defaultProps = {
|
||||
confirmButtonLabel: app.i18n.__('OK'),
|
||||
expandButtonLabel: app.i18n.__('Show More...'),
|
||||
hideButtonLabel: app.i18n.__('Show Less')
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
expanded: false,
|
||||
}
|
||||
}
|
||||
this.state = {
|
||||
expanded: false
|
||||
};
|
||||
}
|
||||
|
||||
toggleExpanded() {
|
||||
this.setState({
|
||||
expanded: !this.state.expanded,
|
||||
});
|
||||
}
|
||||
toggleExpanded() {
|
||||
this.setState({
|
||||
expanded: !this.state.expanded
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Modal type="custom" {... this.props}>
|
||||
{this.props.children}
|
||||
{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() }} />
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Modal type="custom" {...this.props}>
|
||||
{this.props.children}
|
||||
{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();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Modal;
|
||||
|
|
|
@ -1,21 +1,27 @@
|
|||
import React from 'react';
|
||||
|
||||
export class Notice extends React.Component {
|
||||
static propTypes = {
|
||||
isError: React.PropTypes.bool,
|
||||
}
|
||||
static propTypes = {
|
||||
isError: React.PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
isError: false,
|
||||
}
|
||||
static defaultProps = {
|
||||
isError: false
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<section className={'notice ' + (this.props.isError ? 'notice--error ' : '') + (this.props.className || '')}>
|
||||
{this.props.children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<section
|
||||
className={
|
||||
'notice ' +
|
||||
(this.props.isError ? 'notice--error ' : '') +
|
||||
(this.props.className || '')
|
||||
}
|
||||
>
|
||||
{this.props.children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Notice;
|
||||
export default Notice;
|
||||
|
|
|
@ -1,91 +1,109 @@
|
|||
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 = {
|
||||
type: React.PropTypes.string.isRequired,
|
||||
claimed: React.PropTypes.bool,
|
||||
onRewardClaim: React.PropTypes.func,
|
||||
onRewardFailure: React.PropTypes.func
|
||||
}
|
||||
static propTypes = {
|
||||
type: React.PropTypes.string.isRequired,
|
||||
claimed: React.PropTypes.bool,
|
||||
onRewardClaim: React.PropTypes.func,
|
||||
onRewardFailure: React.PropTypes.func
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
claimable: true,
|
||||
pending: false,
|
||||
errorMessage: null
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
claimable: true,
|
||||
pending: false,
|
||||
errorMessage: null
|
||||
};
|
||||
}
|
||||
|
||||
refreshClaimable() {
|
||||
switch(this.props.type) {
|
||||
case 'new_user':
|
||||
this.setState({ claimable: true });
|
||||
return;
|
||||
refreshClaimable() {
|
||||
switch (this.props.type) {
|
||||
case 'new_user':
|
||||
this.setState({ claimable: true });
|
||||
return;
|
||||
|
||||
case 'first_publish':
|
||||
lbry.claim_list_mine().then((list) => {
|
||||
this.setState({
|
||||
claimable: list.length > 0
|
||||
})
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
case 'first_publish':
|
||||
lbry.claim_list_mine().then(list => {
|
||||
this.setState({
|
||||
claimable: list.length > 0
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.refreshClaimable();
|
||||
}
|
||||
componentWillMount() {
|
||||
this.refreshClaimable();
|
||||
}
|
||||
|
||||
claimReward() {
|
||||
this.setState({
|
||||
pending: true
|
||||
})
|
||||
claimReward() {
|
||||
this.setState({
|
||||
pending: true
|
||||
});
|
||||
|
||||
rewards.claimReward(this.props.type).then((reward) => {
|
||||
this.setState({
|
||||
pending: false,
|
||||
errorMessage: null
|
||||
})
|
||||
if (this.props.onRewardClaim) {
|
||||
this.props.onRewardClaim(reward);
|
||||
}
|
||||
}).catch((error) => {
|
||||
this.setState({
|
||||
errorMessage: error.message,
|
||||
pending: false
|
||||
})
|
||||
})
|
||||
}
|
||||
rewards
|
||||
.claimReward(this.props.type)
|
||||
.then(reward => {
|
||||
this.setState({
|
||||
pending: false,
|
||||
errorMessage: null
|
||||
});
|
||||
if (this.props.onRewardClaim) {
|
||||
this.props.onRewardClaim(reward);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.setState({
|
||||
errorMessage: error.message,
|
||||
pending: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
clearError() {
|
||||
if (this.props.onRewardFailure) {
|
||||
this.props.onRewardFailure()
|
||||
}
|
||||
this.setState({
|
||||
errorMessage: null
|
||||
})
|
||||
}
|
||||
clearError() {
|
||||
if (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() }}>
|
||||
{this.state.errorMessage}
|
||||
</Modal>
|
||||
: ''}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
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();
|
||||
}}
|
||||
>
|
||||
{this.state.errorMessage}
|
||||
</Modal>
|
||||
: ''}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) => ({
|
||||
params: selectCurrentParams(state),
|
||||
currentPage: selectCurrentPage(state)
|
||||
})
|
||||
const select = state => ({
|
||||
params: selectCurrentParams(state),
|
||||
currentPage: selectCurrentPage(state)
|
||||
});
|
||||
|
||||
export default connect(select, null)(Router);
|
||||
|
|
|
@ -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} />
|
||||
});
|
||||
};
|
||||
|
||||
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} />,
|
||||
})
|
||||
}
|
||||
|
||||
export default Router
|
||||
export default Router;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,49 +1,45 @@
|
|||
import React from 'react';
|
||||
import Link from 'component/link'
|
||||
import Link from 'component/link';
|
||||
|
||||
class SnackBar extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._displayTime = 5; // in seconds
|
||||
this._hideTimeout = null;
|
||||
}
|
||||
this._displayTime = 5; // in seconds
|
||||
this._hideTimeout = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
navigate,
|
||||
snacks,
|
||||
removeSnack,
|
||||
} = this.props
|
||||
render() {
|
||||
const { navigate, snacks, removeSnack } = this.props;
|
||||
|
||||
if (!snacks.length) {
|
||||
this._hideTimeout = null; //should be unmounting anyway, but be safe?
|
||||
return null;
|
||||
}
|
||||
if (!snacks.length) {
|
||||
this._hideTimeout = null; //should be unmounting anyway, but be safe?
|
||||
return null;
|
||||
}
|
||||
|
||||
const snack = snacks[0];
|
||||
const {
|
||||
message,
|
||||
linkText,
|
||||
linkTarget,
|
||||
} = snack
|
||||
const snack = snacks[0];
|
||||
const { message, linkText, linkTarget } = snack;
|
||||
|
||||
if (this._hideTimeout === null) {
|
||||
this._hideTimeout = setTimeout(() => {
|
||||
this._hideTimeout = null;
|
||||
removeSnack()
|
||||
}, this._displayTime * 1000);
|
||||
}
|
||||
if (this._hideTimeout === null) {
|
||||
this._hideTimeout = setTimeout(() => {
|
||||
this._hideTimeout = null;
|
||||
removeSnack();
|
||||
}, this._displayTime * 1000);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="snack-bar">
|
||||
{message}
|
||||
{linkText && linkTarget &&
|
||||
<Link onClick={() => navigate(linkTarget)} className="snack-bar__action" label={linkText} />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="snack-bar">
|
||||
{message}
|
||||
{linkText &&
|
||||
linkTarget &&
|
||||
<Link
|
||||
onClick={() => navigate(linkTarget)}
|
||||
className="snack-bar__action"
|
||||
label={linkText}
|
||||
/>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SnackBar;
|
||||
export default SnackBar;
|
||||
|
|
|
@ -3,67 +3,80 @@ import lbry from '../lbry.js';
|
|||
import LoadScreen from './load_screen.js';
|
||||
|
||||
export class SplashScreen extends React.Component {
|
||||
static propTypes = {
|
||||
message: React.PropTypes.string,
|
||||
onLoadDone: React.PropTypes.func,
|
||||
}
|
||||
static propTypes = {
|
||||
message: React.PropTypes.string,
|
||||
onLoadDone: React.PropTypes.func
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
details: __('Starting daemon'),
|
||||
message: __("Connecting"),
|
||||
isLagging: false,
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
details: __('Starting daemon'),
|
||||
message: __('Connecting'),
|
||||
isLagging: false
|
||||
};
|
||||
}
|
||||
|
||||
updateStatus() {
|
||||
lbry.status().then((status) => { this._updateStatusCallback(status) });
|
||||
}
|
||||
updateStatus() {
|
||||
lbry.status().then(status => {
|
||||
this._updateStatusCallback(status);
|
||||
});
|
||||
}
|
||||
|
||||
_updateStatusCallback(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"),
|
||||
isLagging: false
|
||||
});
|
||||
_updateStatusCallback(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'),
|
||||
isLagging: false
|
||||
});
|
||||
|
||||
lbry.resolve({uri: "lbry://one"}).then(() => {
|
||||
this.props.onLoadDone();
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
details: startupStatus.message + (startupStatus.is_lagging ? '' : '...'),
|
||||
isLagging: startupStatus.is_lagging,
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.updateStatus();
|
||||
}, 500);
|
||||
}
|
||||
lbry.resolve({ uri: 'lbry://one' }).then(() => {
|
||||
this.props.onLoadDone();
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
details: startupStatus.message + (startupStatus.is_lagging ? '' : '...'),
|
||||
isLagging: startupStatus.is_lagging
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.updateStatus();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
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.")
|
||||
})
|
||||
})
|
||||
}
|
||||
componentDidMount() {
|
||||
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.'
|
||||
)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return <LoadScreen message={this.state.message} details={this.state.details} isWarning={this.state.isLagging} />
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<LoadScreen
|
||||
message={this.state.message}
|
||||
details={this.state.details}
|
||||
isWarning={this.state.isLagging}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SplashScreen;
|
||||
|
|
|
@ -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),
|
||||
})
|
||||
currentPage: selectCurrentPage(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);
|
||||
|
|
|
@ -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)) {
|
||||
links.push(
|
||||
<Link onClick={(event) => navigate(`/${link}`, event)} key={link} className={link == currentPage ? 'sub-header-selected' : 'sub-header-unselected' }>
|
||||
{subLinks[link]}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
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'
|
||||
}
|
||||
>
|
||||
{subLinks[link]}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className={'sub-header' + (modifier ? ' sub-header--' + modifier : '')}>
|
||||
{links}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<nav
|
||||
className={'sub-header' + (modifier ? ' sub-header--' + modifier : '')}
|
||||
>
|
||||
{links}
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default SubHeader
|
||||
export default SubHeader;
|
||||
|
|
|
@ -1,44 +1,55 @@
|
|||
import React from 'react';
|
||||
|
||||
export class ToolTip extends React.Component {
|
||||
static propTypes = {
|
||||
body: React.PropTypes.string.isRequired,
|
||||
label: React.PropTypes.string.isRequired
|
||||
}
|
||||
static propTypes = {
|
||||
body: React.PropTypes.string.isRequired,
|
||||
label: React.PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showTooltip: false,
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
showTooltip: false
|
||||
};
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
this.setState({
|
||||
showTooltip: !this.state.showTooltip,
|
||||
});
|
||||
}
|
||||
handleClick() {
|
||||
this.setState({
|
||||
showTooltip: !this.state.showTooltip
|
||||
});
|
||||
}
|
||||
|
||||
handleTooltipMouseOut() {
|
||||
this.setState({
|
||||
showTooltip: false,
|
||||
});
|
||||
}
|
||||
handleTooltipMouseOut() {
|
||||
this.setState({
|
||||
showTooltip: false
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span className={'tooltip ' + (this.props.className || '')}>
|
||||
<a className="tooltip__link" onClick={() => { this.handleClick() }}>
|
||||
{this.props.label}
|
||||
</a>
|
||||
<div className={'tooltip__body ' + (this.state.showTooltip ? '' : ' hidden')}
|
||||
onMouseOut={() => { this.handleTooltipMouseOut() }}>
|
||||
{this.props.body}
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<span className={'tooltip ' + (this.props.className || '')}>
|
||||
<a
|
||||
className="tooltip__link"
|
||||
onClick={() => {
|
||||
this.handleClick();
|
||||
}}
|
||||
>
|
||||
{this.props.label}
|
||||
</a>
|
||||
<div
|
||||
className={
|
||||
'tooltip__body ' + (this.state.showTooltip ? '' : ' hidden')
|
||||
}
|
||||
onMouseOut={() => {
|
||||
this.handleTooltipMouseOut();
|
||||
}}
|
||||
>
|
||||
{this.props.body}
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ToolTip
|
||||
export default ToolTip;
|
||||
|
|
|
@ -1,25 +1,21 @@
|
|||
import React from 'react'
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { doFetchTransactions } from 'actions/wallet';
|
||||
import {
|
||||
connect
|
||||
} from 'react-redux'
|
||||
import {
|
||||
doFetchTransactions,
|
||||
} from 'actions/wallet'
|
||||
import {
|
||||
selectBalance,
|
||||
selectTransactionItems,
|
||||
selectIsFetchingTransactions,
|
||||
} from 'selectors/wallet'
|
||||
selectBalance,
|
||||
selectTransactionItems,
|
||||
selectIsFetchingTransactions
|
||||
} from 'selectors/wallet';
|
||||
|
||||
import TransactionList from './view'
|
||||
import TransactionList from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
fetchingTransactions: selectIsFetchingTransactions(state),
|
||||
transactionItems: selectTransactionItems(state),
|
||||
})
|
||||
const select = state => ({
|
||||
fetchingTransactions: selectIsFetchingTransactions(state),
|
||||
transactionItems: selectTransactionItems(state)
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
fetchTransactions: () => dispatch(doFetchTransactions())
|
||||
})
|
||||
const perform = dispatch => ({
|
||||
fetchTransactions: () => dispatch(doFetchTransactions())
|
||||
});
|
||||
|
||||
export default connect(select, perform)(TransactionList)
|
||||
export default connect(select, perform)(TransactionList);
|
||||
|
|
|
@ -1,65 +1,73 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Address,
|
||||
BusyMessage,
|
||||
CreditAmount
|
||||
} from 'component/common';
|
||||
import { Address, BusyMessage, CreditAmount } from 'component/common';
|
||||
|
||||
class TransactionList extends React.Component{
|
||||
componentWillMount() {
|
||||
this.props.fetchTransactions()
|
||||
}
|
||||
class TransactionList extends React.Component {
|
||||
componentWillMount() {
|
||||
this.props.fetchTransactions();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
fetchingTransactions,
|
||||
transactionItems,
|
||||
} = this.props
|
||||
render() {
|
||||
const { fetchingTransactions, transactionItems } = this.props;
|
||||
|
||||
const rows = []
|
||||
if (transactionItems.length > 0) {
|
||||
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>
|
||||
<a className="button-text" href={"https://explorer.lbry.io/#!/transaction?id="+item.id}>{item.id.substr(0, 7)}</a>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
}
|
||||
const rows = [];
|
||||
if (transactionItems.length > 0) {
|
||||
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>
|
||||
<a
|
||||
className="button-text"
|
||||
href={'https://explorer.lbry.io/#!/transaction?id=' + item.id}
|
||||
>
|
||||
{item.id.substr(0, 7)}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="card">
|
||||
<div className="card__title-primary">
|
||||
<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">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{__("Amount")}</th>
|
||||
<th>{__("Date")}</th>
|
||||
<th>{__("Time")}</th>
|
||||
<th>{__("Transaction")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows}
|
||||
</tbody>
|
||||
</table>
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<section className="card">
|
||||
<div className="card__title-primary">
|
||||
<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">
|
||||
<thead>
|
||||
<tr>
|
||||
<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;
|
||||
|
|
|
@ -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) => ({
|
||||
downloadUpgrade: () => dispatch(doDownloadUpgrade()),
|
||||
skipUpgrade: () => dispatch(doSkipUpgrade()),
|
||||
})
|
||||
const perform = dispatch => ({
|
||||
downloadUpgrade: () => dispatch(doDownloadUpgrade()),
|
||||
skipUpgrade: () => dispatch(doSkipUpgrade())
|
||||
});
|
||||
|
||||
export default connect(select, perform)(UpgradeModal)
|
||||
export default connect(select, perform)(UpgradeModal);
|
||||
|
|
|
@ -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
|
||||
render() {
|
||||
const { downloadUpgrade, skipUpgrade } = this.props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={true}
|
||||
contentLabel={__("Update available")}
|
||||
type="confirm"
|
||||
confirmButtonLabel={__("Upgrade")}
|
||||
abortButtonLabel={__("Skip")}
|
||||
onConfirmed={downloadUpgrade}
|
||||
onAborted={skipUpgrade}>
|
||||
{__("Your version of LBRY is out of date and may be unreliable or insecure.")}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Modal
|
||||
isOpen={true}
|
||||
contentLabel={__('Update available')}
|
||||
type="confirm"
|
||||
confirmButtonLabel={__('Upgrade')}
|
||||
abortButtonLabel={__('Skip')}
|
||||
onConfirmed={downloadUpgrade}
|
||||
onAborted={skipUpgrade}
|
||||
>
|
||||
{__(
|
||||
'Your version of LBRY is out of date and may be unreliable or insecure.'
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UpgradeModal
|
||||
export default UpgradeModal;
|
||||
|
|
|
@ -1,31 +1,25 @@
|
|||
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(),
|
||||
selectIsResolving = makeSelectIsResolvingForUri();
|
||||
const selectClaim = makeSelectClaimForUri(),
|
||||
selectIsResolving = makeSelectIsResolvingForUri();
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: selectClaim(state, props),
|
||||
isResolvingUri: selectIsResolving(state, props),
|
||||
uri: lbryuri.normalize(props.uri),
|
||||
})
|
||||
const select = (state, props) => ({
|
||||
claim: selectClaim(state, props),
|
||||
isResolvingUri: selectIsResolving(state, props),
|
||||
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);
|
||||
|
|
|
@ -1,70 +1,64 @@
|
|||
import React from 'react';
|
||||
import {Icon} from 'component/common';
|
||||
import { Icon } from 'component/common';
|
||||
|
||||
class UriIndicator extends React.Component{
|
||||
componentWillMount() {
|
||||
this.resolve(this.props)
|
||||
}
|
||||
class UriIndicator extends React.Component {
|
||||
componentWillMount() {
|
||||
this.resolve(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.resolve(nextProps)
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.resolve(nextProps);
|
||||
}
|
||||
|
||||
resolve(props) {
|
||||
const {
|
||||
isResolvingUri,
|
||||
resolveUri,
|
||||
claim,
|
||||
uri,
|
||||
} = props
|
||||
resolve(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
|
||||
render() {
|
||||
const { claim, uri, isResolvingUri } = this.props;
|
||||
|
||||
if (isResolvingUri) {
|
||||
return <span className="empty">Validating...</span>
|
||||
}
|
||||
if (isResolvingUri) {
|
||||
return <span className="empty">Validating...</span>;
|
||||
}
|
||||
|
||||
if (!claim) {
|
||||
return <span className="empty">Unused</span>
|
||||
}
|
||||
if (!claim) {
|
||||
return <span className="empty">Unused</span>;
|
||||
}
|
||||
|
||||
const {
|
||||
channel_name: channelName,
|
||||
has_signature: hasSignature,
|
||||
signature_is_valid: signatureIsValid,
|
||||
} = claim
|
||||
const {
|
||||
channel_name: channelName,
|
||||
has_signature: hasSignature,
|
||||
signature_is_valid: signatureIsValid
|
||||
} = claim;
|
||||
|
||||
if (!hasSignature || !channelName) {
|
||||
return <span className="empty">Anonymous</span>;
|
||||
}
|
||||
if (!hasSignature || !channelName) {
|
||||
return <span className="empty">Anonymous</span>;
|
||||
}
|
||||
|
||||
let icon, modifier;
|
||||
if (signatureIsValid) {
|
||||
modifier = 'valid';
|
||||
} else {
|
||||
icon = 'icon-times-circle';
|
||||
modifier = 'invalid';
|
||||
}
|
||||
let icon, modifier;
|
||||
if (signatureIsValid) {
|
||||
modifier = 'valid';
|
||||
} else {
|
||||
icon = 'icon-times-circle';
|
||||
modifier = 'invalid';
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
{channelName} {' '}
|
||||
{ !signatureIsValid ?
|
||||
<Icon icon={icon} className={`channel-indicator__icon channel-indicator__icon--${modifier}`} /> :
|
||||
'' }
|
||||
</span>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<span>
|
||||
{channelName} {' '}
|
||||
{!signatureIsValid
|
||||
? <Icon
|
||||
icon={icon}
|
||||
className={`channel-indicator__icon channel-indicator__icon--${modifier}`}
|
||||
/>
|
||||
: ''}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UriIndicator;
|
||||
export default UriIndicator;
|
||||
|
|
|
@ -1,57 +1,45 @@
|
|||
import React from 'react'
|
||||
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 {
|
||||
connect,
|
||||
} from 'react-redux'
|
||||
makeSelectMetadataForUri,
|
||||
makeSelectContentTypeForUri
|
||||
} from 'selectors/claims';
|
||||
import {
|
||||
doCloseModal,
|
||||
} from 'actions/app'
|
||||
import {
|
||||
selectCurrentModal,
|
||||
} from 'selectors/app'
|
||||
import {
|
||||
doPurchaseUri,
|
||||
doLoadVideo,
|
||||
} from 'actions/content'
|
||||
import {
|
||||
makeSelectMetadataForUri,
|
||||
makeSelectContentTypeForUri,
|
||||
} from 'selectors/claims'
|
||||
import {
|
||||
makeSelectFileInfoForUri,
|
||||
makeSelectLoadingForUri,
|
||||
makeSelectDownloadingForUri,
|
||||
} from 'selectors/file_info'
|
||||
import {
|
||||
makeSelectCostInfoForUri,
|
||||
} from 'selectors/cost_info'
|
||||
import Video from './view'
|
||||
|
||||
makeSelectFileInfoForUri,
|
||||
makeSelectLoadingForUri,
|
||||
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),
|
||||
fileInfo: selectFileInfo(state, props),
|
||||
metadata: selectMetadata(state, props),
|
||||
modal: selectCurrentModal(state),
|
||||
isLoading: selectIsLoading(state, props),
|
||||
isDownloading: selectIsDownloading(state, props),
|
||||
contentType: selectContentType(state, props),
|
||||
})
|
||||
const select = (state, props) => ({
|
||||
costInfo: selectCostInfo(state, props),
|
||||
fileInfo: selectFileInfo(state, props),
|
||||
metadata: selectMetadata(state, props),
|
||||
modal: selectCurrentModal(state),
|
||||
isLoading: selectIsLoading(state, props),
|
||||
isDownloading: selectIsDownloading(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);
|
||||
|
|
|
@ -1,46 +1,42 @@
|
|||
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)
|
||||
}
|
||||
onPurchaseConfirmed() {
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
||||
onWatchClick() {
|
||||
this.props.purchaseUri(this.props.uri).then(() => {
|
||||
if (!this.props.modal) {
|
||||
this.props.startPlaying();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
button,
|
||||
label,
|
||||
className,
|
||||
metadata,
|
||||
metadata: {
|
||||
title,
|
||||
},
|
||||
uri,
|
||||
modal,
|
||||
closeModal,
|
||||
isLoading,
|
||||
costInfo,
|
||||
fileInfo,
|
||||
mediaType,
|
||||
} = this.props
|
||||
render() {
|
||||
const {
|
||||
button,
|
||||
label,
|
||||
className,
|
||||
metadata,
|
||||
metadata: { title },
|
||||
uri,
|
||||
modal,
|
||||
closeModal,
|
||||
isLoading,
|
||||
costInfo,
|
||||
fileInfo,
|
||||
mediaType
|
||||
} = this.props;
|
||||
|
||||
/*
|
||||
/*
|
||||
title={
|
||||
isLoading ? "Video is Loading" :
|
||||
!costInfo ? "Waiting on cost info..." :
|
||||
|
@ -48,135 +44,171 @@ 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 }
|
||||
disabled={disabled}
|
||||
label={label ? label : ""}
|
||||
className="video__play-button"
|
||||
icon={icon}
|
||||
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")}
|
||||
onConfirmed={this.onPurchaseConfirmed.bind(this)}
|
||||
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 :(")}
|
||||
</Modal>
|
||||
</div>);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Link
|
||||
button={button ? button : null}
|
||||
disabled={disabled}
|
||||
label={label ? label : ''}
|
||||
className="video__play-button"
|
||||
icon={icon}
|
||||
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')}
|
||||
onConfirmed={this.onPurchaseConfirmed.bind(this)}
|
||||
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 :(')}
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Video extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = { isPlaying: false }
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { isPlaying: false };
|
||||
}
|
||||
|
||||
startPlaying() {
|
||||
this.setState({
|
||||
isPlaying: true
|
||||
})
|
||||
}
|
||||
startPlaying() {
|
||||
this.setState({
|
||||
isPlaying: true
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
metadata,
|
||||
isLoading,
|
||||
isDownloading,
|
||||
fileInfo,
|
||||
contentType,
|
||||
} = this.props
|
||||
const {
|
||||
isPlaying = false,
|
||||
} = this.state
|
||||
render() {
|
||||
const {
|
||||
metadata,
|
||||
isLoading,
|
||||
isDownloading,
|
||||
fileInfo,
|
||||
contentType
|
||||
} = this.props;
|
||||
const { isPlaying = false } = this.state;
|
||||
|
||||
const isReadyToPlay = fileInfo && fileInfo.written_bytes > 0
|
||||
const mediaType = lbry.getMediaType(contentType, 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.")
|
||||
} 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")
|
||||
} else if (isDownloading) {
|
||||
loadStatusMessage = __("Downloading stream... not long left now!")
|
||||
}
|
||||
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"
|
||||
);
|
||||
} else if (isDownloading) {
|
||||
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"
|
||||
} else {
|
||||
if (!isPlaying) klassName += "video-embedded"
|
||||
}
|
||||
const poster = metadata.thumbnail
|
||||
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';
|
||||
}
|
||||
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>
|
||||
}</div>
|
||||
);
|
||||
}
|
||||
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>}
|
||||
</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 file = {
|
||||
name: filename,
|
||||
createReadStream: (opts) => {
|
||||
return fs.createReadStream(downloadPath, opts)
|
||||
}
|
||||
}
|
||||
player.append(file, elem, {
|
||||
autoplay: true,
|
||||
controls: true,
|
||||
})
|
||||
}
|
||||
componentDidMount() {
|
||||
const elem = this.refs.media;
|
||||
const { downloadPath, filename } = this.props;
|
||||
const file = {
|
||||
name: filename,
|
||||
createReadStream: opts => {
|
||||
return fs.createReadStream(downloadPath, opts);
|
||||
}
|
||||
};
|
||||
player.append(file, elem, {
|
||||
autoplay: true,
|
||||
controls: true
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
downloadPath,
|
||||
mediaType,
|
||||
poster,
|
||||
} = this.props
|
||||
render() {
|
||||
const { downloadPath, mediaType, poster } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{["audio", "application"].indexOf(mediaType) !== -1 && <Thumbnail src={poster} className="video-embedded" />}
|
||||
<div ref="media" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{['audio', 'application'].indexOf(mediaType) !== -1 &&
|
||||
<Thumbnail src={poster} className="video-embedded" />}
|
||||
<div ref="media" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Video
|
||||
export default Video;
|
||||
|
|
|
@ -1,25 +1,20 @@
|
|||
import React from 'react'
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { doCheckAddressIsMine, doGetNewAddress } from 'actions/wallet';
|
||||
import {
|
||||
connect
|
||||
} from 'react-redux'
|
||||
import {
|
||||
doCheckAddressIsMine,
|
||||
doGetNewAddress,
|
||||
} from 'actions/wallet'
|
||||
import {
|
||||
selectReceiveAddress,
|
||||
selectGettingNewAddress
|
||||
} from 'selectors/wallet'
|
||||
import WalletPage from './view'
|
||||
selectReceiveAddress,
|
||||
selectGettingNewAddress
|
||||
} from 'selectors/wallet';
|
||||
import WalletPage from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
receiveAddress: selectReceiveAddress(state),
|
||||
gettingNewAddress: selectGettingNewAddress(state),
|
||||
})
|
||||
const select = state => ({
|
||||
receiveAddress: selectReceiveAddress(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);
|
||||
|
|
|
@ -1,41 +1,49 @@
|
|||
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)
|
||||
}
|
||||
componentWillMount() {
|
||||
this.props.checkAddressIsMine(this.props.receiveAddress);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
receiveAddress,
|
||||
getNewAddress,
|
||||
gettingNewAddress,
|
||||
} = this.props
|
||||
render() {
|
||||
const { receiveAddress, getNewAddress, gettingNewAddress } = this.props;
|
||||
|
||||
return (
|
||||
<section className="card">
|
||||
<div className="card__title-primary">
|
||||
<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} />
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<section className="card">
|
||||
<div className="card__title-primary">
|
||||
<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}
|
||||
/>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WalletAddress
|
||||
export default WalletAddress;
|
||||
|
|
|
@ -1,36 +1,31 @@
|
|||
import React from 'react'
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { doCloseModal } from 'actions/app';
|
||||
import {
|
||||
connect
|
||||
} from 'react-redux'
|
||||
doSendDraftTransaction,
|
||||
doSetDraftTransactionAmount,
|
||||
doSetDraftTransactionAddress
|
||||
} from 'actions/wallet';
|
||||
import { selectCurrentModal } from 'selectors/app';
|
||||
import {
|
||||
doCloseModal,
|
||||
} from 'actions/app'
|
||||
import {
|
||||
doSendDraftTransaction,
|
||||
doSetDraftTransactionAmount,
|
||||
doSetDraftTransactionAddress,
|
||||
} from 'actions/wallet'
|
||||
import {
|
||||
selectCurrentModal,
|
||||
} from 'selectors/app'
|
||||
import {
|
||||
selectDraftTransactionAmount,
|
||||
selectDraftTransactionAddress,
|
||||
} from 'selectors/wallet'
|
||||
selectDraftTransactionAmount,
|
||||
selectDraftTransactionAddress
|
||||
} from 'selectors/wallet';
|
||||
|
||||
import WalletSend from './view'
|
||||
import WalletSend from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
modal: selectCurrentModal(state),
|
||||
address: selectDraftTransactionAddress(state),
|
||||
amount: selectDraftTransactionAmount(state),
|
||||
})
|
||||
const select = state => ({
|
||||
modal: selectCurrentModal(state),
|
||||
address: selectDraftTransactionAddress(state),
|
||||
amount: selectDraftTransactionAmount(state)
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
closeModal: () => dispatch(doCloseModal()),
|
||||
sendToAddress: () => dispatch(doSendDraftTransaction()),
|
||||
setAmount: (event) => dispatch(doSetDraftTransactionAmount(event.target.value)),
|
||||
setAddress: (event) => dispatch(doSetDraftTransactionAddress(event.target.value)),
|
||||
})
|
||||
const perform = dispatch => ({
|
||||
closeModal: () => dispatch(doCloseModal()),
|
||||
sendToAddress: () => dispatch(doSendDraftTransaction()),
|
||||
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);
|
||||
|
|
|
@ -1,49 +1,85 @@
|
|||
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 {
|
||||
sendToAddress,
|
||||
closeModal,
|
||||
modal,
|
||||
setAmount,
|
||||
setAddress,
|
||||
amount,
|
||||
address,
|
||||
} = props
|
||||
const WalletSend = props => {
|
||||
const {
|
||||
sendToAddress,
|
||||
closeModal,
|
||||
modal,
|
||||
setAmount,
|
||||
setAddress,
|
||||
amount,
|
||||
address
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<section className="card">
|
||||
<form onSubmit={sendToAddress}>
|
||||
<div className="card__title-primary">
|
||||
<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} />
|
||||
</div>
|
||||
<div className="card__content">
|
||||
<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' />
|
||||
</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>}
|
||||
{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>}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<section className="card">
|
||||
<form onSubmit={sendToAddress}>
|
||||
<div className="card__title-primary">
|
||||
<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}
|
||||
/>
|
||||
</div>
|
||||
<div className="card__content">
|
||||
<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" />
|
||||
</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>}
|
||||
{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>}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default WalletSend
|
||||
export default WalletSend;
|
||||
|
|
|
@ -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) => ({
|
||||
address: selectWunderBarAddress(state),
|
||||
icon: selectWunderBarIcon(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);
|
||||
|
|
|
@ -1,160 +1,170 @@
|
|||
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
|
||||
}
|
||||
static propTypes = {
|
||||
onSearch: React.PropTypes.func.isRequired,
|
||||
onSubmit: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._userTypingTimer = null;
|
||||
this._isSearchDispatchPending = false;
|
||||
this._input = null;
|
||||
this._stateBeforeSearch = null;
|
||||
this._resetOnNextBlur = true;
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.onFocus = this.onFocus.bind(this);
|
||||
this.onBlur = this.onBlur.bind(this);
|
||||
this.onKeyPress = this.onKeyPress.bind(this);
|
||||
this.onReceiveRef = this.onReceiveRef.bind(this);
|
||||
this.state = {
|
||||
address: this.props.address,
|
||||
icon: this.props.icon
|
||||
};
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._userTypingTimer = null;
|
||||
this._isSearchDispatchPending = false;
|
||||
this._input = null;
|
||||
this._stateBeforeSearch = null;
|
||||
this._resetOnNextBlur = true;
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.onFocus = this.onFocus.bind(this);
|
||||
this.onBlur = this.onBlur.bind(this);
|
||||
this.onKeyPress = this.onKeyPress.bind(this);
|
||||
this.onReceiveRef = this.onReceiveRef.bind(this);
|
||||
this.state = {
|
||||
address: this.props.address,
|
||||
icon: this.props.icon
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.userTypingTimer) {
|
||||
clearTimeout(this._userTypingTimer);
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
if (this.userTypingTimer) {
|
||||
clearTimeout(this._userTypingTimer);
|
||||
}
|
||||
}
|
||||
|
||||
onChange(event) {
|
||||
onChange(event) {
|
||||
if (this._userTypingTimer) {
|
||||
clearTimeout(this._userTypingTimer);
|
||||
}
|
||||
|
||||
if (this._userTypingTimer)
|
||||
{
|
||||
clearTimeout(this._userTypingTimer);
|
||||
}
|
||||
this.setState({ address: event.target.value });
|
||||
|
||||
this.setState({ address: event.target.value })
|
||||
this._isSearchDispatchPending = true;
|
||||
|
||||
this._isSearchDispatchPending = true;
|
||||
let searchQuery = event.target.value;
|
||||
|
||||
let searchQuery = event.target.value;
|
||||
this._userTypingTimer = setTimeout(() => {
|
||||
const hasQuery = searchQuery.length === 0;
|
||||
this._resetOnNextBlur = hasQuery;
|
||||
this._isSearchDispatchPending = false;
|
||||
if (searchQuery) {
|
||||
this.props.onSearch(searchQuery);
|
||||
}
|
||||
}, WunderBar.TYPING_TIMEOUT); // 800ms delay, tweak for faster/slower
|
||||
}
|
||||
|
||||
this._userTypingTimer = setTimeout(() => {
|
||||
const hasQuery = searchQuery.length === 0;
|
||||
this._resetOnNextBlur = hasQuery;
|
||||
this._isSearchDispatchPending = false;
|
||||
if (searchQuery) {
|
||||
this.props.onSearch(searchQuery);
|
||||
}
|
||||
}, WunderBar.TYPING_TIMEOUT); // 800ms delay, tweak for faster/slower
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (
|
||||
nextProps.viewingPage !== this.props.viewingPage ||
|
||||
nextProps.address != this.props.address
|
||||
) {
|
||||
this.setState({ address: nextProps.address, icon: nextProps.icon });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.viewingPage !== this.props.viewingPage || nextProps.address != this.props.address) {
|
||||
this.setState({ address: nextProps.address, icon: nextProps.icon });
|
||||
}
|
||||
}
|
||||
onFocus() {
|
||||
this._stateBeforeSearch = this.state;
|
||||
let newState = {
|
||||
icon: 'icon-search',
|
||||
isActive: true
|
||||
};
|
||||
|
||||
onFocus() {
|
||||
this._stateBeforeSearch = this.state;
|
||||
let newState = {
|
||||
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
|
||||
newState.address = '';
|
||||
}
|
||||
this.setState(newState);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
newState.address = '';
|
||||
}
|
||||
this.setState(newState);
|
||||
}
|
||||
onBlur() {
|
||||
if (this._isSearchDispatchPending) {
|
||||
setTimeout(() => {
|
||||
this.onBlur();
|
||||
}, WunderBar.TYPING_TIMEOUT + 1);
|
||||
} else {
|
||||
let commonState = { isActive: false };
|
||||
if (this._resetOnNextBlur) {
|
||||
this.setState(Object.assign({}, this._stateBeforeSearch, commonState));
|
||||
this._input.value = this.state.address;
|
||||
} else {
|
||||
this._resetOnNextBlur = true;
|
||||
this._stateBeforeSearch = this.state;
|
||||
this.setState(commonState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBlur() {
|
||||
if (this._isSearchDispatchPending) {
|
||||
setTimeout(() => {
|
||||
this.onBlur();
|
||||
}, WunderBar.TYPING_TIMEOUT + 1)
|
||||
} else {
|
||||
let commonState = {isActive: false};
|
||||
if (this._resetOnNextBlur) {
|
||||
this.setState(Object.assign({}, this._stateBeforeSearch, commonState));
|
||||
this._input.value = this.state.address;
|
||||
}
|
||||
else {
|
||||
this._resetOnNextBlur = true;
|
||||
this._stateBeforeSearch = this.state;
|
||||
this.setState(commonState);
|
||||
}
|
||||
}
|
||||
}
|
||||
componentDidUpdate() {
|
||||
if (this._input) {
|
||||
const start = this._input.selectionStart,
|
||||
end = this._input.selectionEnd;
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this._input) {
|
||||
const start = this._input.selectionStart,
|
||||
end = this._input.selectionEnd;
|
||||
this._input.value = this.state.address; //this causes cursor to go to end of input
|
||||
|
||||
this._input.value = this.state.address; //this causes cursor to go to end of input
|
||||
this._input.setSelectionRange(start, end);
|
||||
|
||||
this._input.setSelectionRange(start, end);
|
||||
if (this._focusPending) {
|
||||
this._input.select();
|
||||
this._focusPending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this._focusPending) {
|
||||
this._input.select();
|
||||
this._focusPending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
onKeyPress(event) {
|
||||
if (event.charCode == 13 && this._input.value) {
|
||||
let uri = null,
|
||||
method = 'onSubmit';
|
||||
|
||||
onKeyPress(event) {
|
||||
if (event.charCode == 13 && this._input.value) {
|
||||
this._resetOnNextBlur = false;
|
||||
clearTimeout(this._userTypingTimer);
|
||||
|
||||
let uri = null,
|
||||
method = "onSubmit";
|
||||
try {
|
||||
uri = lbryuri.normalize(this._input.value);
|
||||
this.setState({ value: uri });
|
||||
} catch (error) {
|
||||
//then it's not a valid URL, so let's search
|
||||
uri = this._input.value;
|
||||
method = 'onSearch';
|
||||
}
|
||||
|
||||
this._resetOnNextBlur = false;
|
||||
clearTimeout(this._userTypingTimer);
|
||||
this.props[method](uri);
|
||||
this._input.blur();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
uri = lbryuri.normalize(this._input.value);
|
||||
this.setState({ value: uri });
|
||||
} catch (error) { //then it's not a valid URL, so let's search
|
||||
uri = this._input.value;
|
||||
method = "onSearch";
|
||||
}
|
||||
onReceiveRef(ref) {
|
||||
this._input = ref;
|
||||
}
|
||||
|
||||
this.props[method](uri);
|
||||
this._input.blur();
|
||||
}
|
||||
}
|
||||
|
||||
onReceiveRef(ref) {
|
||||
this._input = ref;
|
||||
}
|
||||
|
||||
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"
|
||||
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")} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
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"
|
||||
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')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WunderBar;
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
module.exports = {
|
||||
}
|
||||
module.exports = {};
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
module.exports = {
|
||||
}
|
||||
module.exports = {};
|
||||
|
|
|
@ -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';
|
||||
|
|
138
ui/js/jsonrpc.js
138
ui/js/jsonrpc.js
|
@ -1,75 +1,87 @@
|
|||
const jsonrpc = {};
|
||||
|
||||
jsonrpc.call = function (connectionString, method, params, callback, errorCallback, connectFailedCallback, timeout) {
|
||||
var xhr = new XMLHttpRequest;
|
||||
if (typeof connectFailedCallback !== 'undefined') {
|
||||
if (timeout) {
|
||||
xhr.timeout = timeout;
|
||||
}
|
||||
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) {
|
||||
connectFailedCallback(e);
|
||||
});
|
||||
xhr.addEventListener('timeout', function() {
|
||||
connectFailedCallback(new Error(__('XMLHttpRequest connection timed out')));
|
||||
})
|
||||
}
|
||||
xhr.addEventListener('load', function() {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
xhr.addEventListener('error', function(e) {
|
||||
connectFailedCallback(e);
|
||||
});
|
||||
xhr.addEventListener('timeout', function() {
|
||||
connectFailedCallback(
|
||||
new Error(__('XMLHttpRequest connection timed out'))
|
||||
);
|
||||
});
|
||||
}
|
||||
xhr.addEventListener('load', function() {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
|
||||
if (response.error) {
|
||||
if (errorCallback) {
|
||||
errorCallback(response.error);
|
||||
} else {
|
||||
var errorEvent = new CustomEvent('unhandledError', {
|
||||
detail: {
|
||||
connectionString: connectionString,
|
||||
method: method,
|
||||
params: params,
|
||||
code: response.error.code,
|
||||
message: response.error.message,
|
||||
data: response.error.data
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(errorEvent)
|
||||
}
|
||||
} else if (callback) {
|
||||
callback(response.result);
|
||||
}
|
||||
});
|
||||
if (response.error) {
|
||||
if (errorCallback) {
|
||||
errorCallback(response.error);
|
||||
} else {
|
||||
var errorEvent = new CustomEvent('unhandledError', {
|
||||
detail: {
|
||||
connectionString: connectionString,
|
||||
method: method,
|
||||
params: params,
|
||||
code: response.error.code,
|
||||
message: response.error.message,
|
||||
data: response.error.data
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(errorEvent);
|
||||
}
|
||||
} else if (callback) {
|
||||
callback(response.result);
|
||||
}
|
||||
});
|
||||
|
||||
if (connectFailedCallback) {
|
||||
xhr.addEventListener('error', function (event) {
|
||||
connectFailedCallback(event);
|
||||
});
|
||||
} else {
|
||||
xhr.addEventListener('error', function (event) {
|
||||
var errorEvent = new CustomEvent('unhandledError', {
|
||||
detail: {
|
||||
connectionString: connectionString,
|
||||
method: method,
|
||||
params: params,
|
||||
code: xhr.status,
|
||||
message: __('Connection to API server failed')
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(errorEvent);
|
||||
});
|
||||
}
|
||||
if (connectFailedCallback) {
|
||||
xhr.addEventListener('error', function(event) {
|
||||
connectFailedCallback(event);
|
||||
});
|
||||
} else {
|
||||
xhr.addEventListener('error', function(event) {
|
||||
var errorEvent = new CustomEvent('unhandledError', {
|
||||
detail: {
|
||||
connectionString: connectionString,
|
||||
method: method,
|
||||
params: params,
|
||||
code: xhr.status,
|
||||
message: __('Connection to API server failed')
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(errorEvent);
|
||||
});
|
||||
}
|
||||
|
||||
const counter = parseInt(sessionStorage.getItem('JSONRPCCounter') || 0);
|
||||
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.open('POST', connectionString, true);
|
||||
xhr.send(
|
||||
JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method: method,
|
||||
params: params,
|
||||
id: counter
|
||||
})
|
||||
);
|
||||
|
||||
sessionStorage.setItem('JSONRPCCounter', counter + 1);
|
||||
sessionStorage.setItem('JSONRPCCounter', counter + 1);
|
||||
|
||||
return xhr
|
||||
return xhr;
|
||||
};
|
||||
|
||||
export default jsonrpc;
|
||||
|
|
718
ui/js/lbry.js
718
ui/js/lbry.js
|
@ -2,60 +2,68 @@ 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 = {
|
||||
isConnected: false,
|
||||
daemonConnectionString: 'http://localhost:5279/lbryapi',
|
||||
pendingPublishTimeout: 20 * 60 * 1000,
|
||||
defaultClientSettings: {
|
||||
showNsfw: false,
|
||||
showUnavailable: true,
|
||||
debug: false,
|
||||
useCustomLighthouseServers: false,
|
||||
customLighthouseServers: [],
|
||||
showDeveloperMenu: false,
|
||||
language: 'en',
|
||||
}
|
||||
isConnected: false,
|
||||
daemonConnectionString: 'http://localhost:5279/lbryapi',
|
||||
pendingPublishTimeout: 20 * 60 * 1000,
|
||||
defaultClientSettings: {
|
||||
showNsfw: false,
|
||||
showUnavailable: true,
|
||||
debug: false,
|
||||
useCustomLighthouseServers: false,
|
||||
customLighthouseServers: [],
|
||||
showDeveloperMenu: false,
|
||||
language: 'en'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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}) {
|
||||
let uri;
|
||||
if (channel_name) {
|
||||
uri = lbryuri.build({name: channel_name, path: name}, false);
|
||||
} else {
|
||||
uri = lbryuri.build({name: name}, false);
|
||||
}
|
||||
const pendingPublishes = getLocal('pendingPublishes') || [];
|
||||
const newPendingPublish = {
|
||||
name, channel_name,
|
||||
claim_id: 'pending_claim_' + uri,
|
||||
txid: 'pending_' + uri,
|
||||
nout: 0,
|
||||
outpoint: 'pending_' + uri + ':0',
|
||||
time: Date.now(),
|
||||
};
|
||||
setLocal('pendingPublishes', [...pendingPublishes, newPendingPublish]);
|
||||
return newPendingPublish;
|
||||
function savePendingPublish({ name, channel_name }) {
|
||||
let uri;
|
||||
if (channel_name) {
|
||||
uri = lbryuri.build({ name: channel_name, path: name }, false);
|
||||
} else {
|
||||
uri = lbryuri.build({ name: name }, false);
|
||||
}
|
||||
const pendingPublishes = getLocal('pendingPublishes') || [];
|
||||
const newPendingPublish = {
|
||||
name,
|
||||
channel_name,
|
||||
claim_id: 'pending_claim_' + uri,
|
||||
txid: 'pending_' + uri,
|
||||
nout: 0,
|
||||
outpoint: 'pending_' + uri + ':0',
|
||||
time: Date.now()
|
||||
};
|
||||
setLocal('pendingPublishes', [...pendingPublishes, newPendingPublish]);
|
||||
return newPendingPublish;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 pubMatches(pub) {
|
||||
return pub.outpoint === outpoint || (pub.name === name && (!channel_name || pub.channel_name === channel_name));
|
||||
}
|
||||
function removePendingPublishIfNeeded({ name, channel_name, outpoint }) {
|
||||
function pubMatches(pub) {
|
||||
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))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,74 +71,111 @@ function removePendingPublishIfNeeded({name, channel_name, outpoint}) {
|
|||
* removes them from the list.
|
||||
*/
|
||||
lbry.getPendingPublishes = function() {
|
||||
const pendingPublishes = getLocal('pendingPublishes') || [];
|
||||
const newPendingPublishes = pendingPublishes.filter(pub => Date.now() - pub.time <= lbry.pendingPublishTimeout);
|
||||
setLocal('pendingPublishes', newPendingPublishes);
|
||||
return newPendingPublishes;
|
||||
}
|
||||
const pendingPublishes = getLocal('pendingPublishes') || [];
|
||||
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}) {
|
||||
const pendingPublishes = lbry.getPendingPublishes();
|
||||
return pendingPublishes.find(
|
||||
pub => pub.outpoint === outpoint || (pub.name === name && (!channel_name || pub.channel_name === channel_name))
|
||||
) || null;
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
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) => {
|
||||
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
|
||||
setTimeout(() => {
|
||||
tryNum++;
|
||||
checkDaemonStarted();
|
||||
}, tryNum < 50 ? 400 : 1000);
|
||||
} else {
|
||||
reject(new Error('Unable to connect to LBRY'));
|
||||
}
|
||||
}
|
||||
|
||||
function checkDaemonStartedFailed() {
|
||||
if (tryNum <= 100) { // Move # of tries into constant or config option
|
||||
setTimeout(() => {
|
||||
tryNum++
|
||||
checkDaemonStarted();
|
||||
}, tryNum < 50 ? 400 : 1000);
|
||||
}
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
// Check every half second to see if the daemon is accepting connections
|
||||
function checkDaemonStarted() {
|
||||
lbry.call('status', {}, resolve, checkDaemonStartedFailed, checkDaemonStartedFailed)
|
||||
}
|
||||
checkDaemonStarted();
|
||||
});
|
||||
}
|
||||
|
||||
checkDaemonStarted();
|
||||
});
|
||||
}
|
||||
|
||||
return lbry._connectPromise;
|
||||
}
|
||||
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,48 +187,49 @@ 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, {})
|
||||
if (lbry.costPromiseCache[uri] === undefined) {
|
||||
lbry.costPromiseCache[uri] = new Promise((resolve, reject) => {
|
||||
const COST_INFO_CACHE_KEY = 'cost_info_cache';
|
||||
let costInfoCache = getSession(COST_INFO_CACHE_KEY, {});
|
||||
|
||||
function cacheAndResolve(cost, includesData) {
|
||||
costInfoCache[uri] = {cost, includesData};
|
||||
setSession(COST_INFO_CACHE_KEY, costInfoCache);
|
||||
resolve({cost, includesData});
|
||||
}
|
||||
function cacheAndResolve(cost, includesData) {
|
||||
costInfoCache[uri] = { cost, includesData };
|
||||
setSession(COST_INFO_CACHE_KEY, costInfoCache);
|
||||
resolve({ cost, includesData });
|
||||
}
|
||||
|
||||
if (!uri) {
|
||||
return reject(new Error(`URI required.`));
|
||||
}
|
||||
if (!uri) {
|
||||
return reject(new Error(`URI required.`));
|
||||
}
|
||||
|
||||
if (costInfoCache[uri] && costInfoCache[uri].cost) {
|
||||
return resolve(costInfoCache[uri])
|
||||
}
|
||||
if (costInfoCache[uri] && costInfoCache[uri].cost) {
|
||||
return resolve(costInfoCache[uri]);
|
||||
}
|
||||
|
||||
function getCost(uri, size) {
|
||||
lbry.stream_cost_estimate({uri, ... size !== null ? {size} : {}}).then((cost) => {
|
||||
cacheAndResolve(cost, size !== null);
|
||||
}, reject);
|
||||
}
|
||||
function getCost(uri, size) {
|
||||
lbry
|
||||
.stream_cost_estimate({ uri, ...(size !== null ? { size } : {}) })
|
||||
.then(cost => {
|
||||
cacheAndResolve(cost, size !== null);
|
||||
}, reject);
|
||||
}
|
||||
|
||||
const uriObj = lbryuri.parse(uri);
|
||||
const name = uriObj.path || uriObj.name;
|
||||
const uriObj = lbryuri.parse(uri);
|
||||
const name = uriObj.path || uriObj.name;
|
||||
|
||||
lighthouse.get_size_for_name(name).then((size) => {
|
||||
if (size) {
|
||||
getCost(name, size);
|
||||
}
|
||||
else {
|
||||
getCost(name, null);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
return lbry.costPromiseCache[uri];
|
||||
}
|
||||
lighthouse.get_size_for_name(name).then(size => {
|
||||
if (size) {
|
||||
getCost(name, size);
|
||||
} else {
|
||||
getCost(name, null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return lbry.costPromiseCache[uri];
|
||||
};
|
||||
|
||||
/**
|
||||
* Publishes a file. The optional fileListedCallback is called when the file becomes available in
|
||||
|
@ -192,125 +238,144 @@ 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) => {
|
||||
if (returnedPending) {
|
||||
return;
|
||||
}
|
||||
lbry.publish = function(
|
||||
params,
|
||||
fileListedCallback,
|
||||
publishedCallback,
|
||||
errorCallback
|
||||
) {
|
||||
lbry.call(
|
||||
'publish',
|
||||
params,
|
||||
result => {
|
||||
if (returnedPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(returnPendingTimeout);
|
||||
publishedCallback(result);
|
||||
}, (err) => {
|
||||
if (returnedPending) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(returnPendingTimeout);
|
||||
publishedCallback(result);
|
||||
},
|
||||
err => {
|
||||
if (returnedPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(returnPendingTimeout);
|
||||
errorCallback(err);
|
||||
});
|
||||
clearTimeout(returnPendingTimeout);
|
||||
errorCallback(err);
|
||||
}
|
||||
);
|
||||
|
||||
let returnedPending = false;
|
||||
// Give a short grace period in case publish() returns right away or (more likely) gives an error
|
||||
const returnPendingTimeout = setTimeout(() => {
|
||||
returnedPending = true;
|
||||
let returnedPending = false;
|
||||
// Give a short grace period in case publish() returns right away or (more likely) gives an error
|
||||
const returnPendingTimeout = setTimeout(() => {
|
||||
returnedPending = true;
|
||||
|
||||
if (publishedCallback) {
|
||||
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});
|
||||
fileListedCallback(true);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
if (publishedCallback) {
|
||||
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
|
||||
});
|
||||
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));
|
||||
}
|
||||
return outSettings;
|
||||
}
|
||||
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);
|
||||
}
|
||||
return outSettings;
|
||||
};
|
||||
|
||||
lbry.getClientSetting = function(setting) {
|
||||
var localStorageVal = localStorage.getItem('setting_' + setting);
|
||||
if (setting == 'showDeveloperMenu')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return (localStorageVal === null ? lbry.defaultClientSettings[setting] : JSON.parse(localStorageVal));
|
||||
}
|
||||
var localStorageVal = localStorage.getItem('setting_' + setting);
|
||||
if (setting == 'showDeveloperMenu') {
|
||||
return true;
|
||||
}
|
||||
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]);
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
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', {
|
||||
message: message
|
||||
}, callback);
|
||||
}
|
||||
lbry.call(
|
||||
'report_bug',
|
||||
{
|
||||
message: message
|
||||
},
|
||||
callback
|
||||
);
|
||||
};
|
||||
|
||||
//utilities
|
||||
lbry.formatCredits = function(amount, precision)
|
||||
{
|
||||
return amount.toFixed(precision || 1).replace(/\.?0+$/, '');
|
||||
}
|
||||
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.toLowerCase().replace(/[^a-z0-9\-]/g, '');
|
||||
return name;
|
||||
}
|
||||
// Converts LBRY name to standard format (all lower case, no special characters, spaces replaced by dashes)
|
||||
name = name.replace('/s+/g', '-');
|
||||
name = name.toLowerCase().replace(/[^a-z0-9\-]/g, '');
|
||||
return name;
|
||||
};
|
||||
|
||||
|
||||
lbry.imagePath = function(file)
|
||||
{
|
||||
return 'img/' + file;
|
||||
}
|
||||
lbry.imagePath = function(file) {
|
||||
return 'img/' + file;
|
||||
};
|
||||
|
||||
lbry.getMediaType = function(contentType, fileName) {
|
||||
if (contentType) {
|
||||
return /^[^/]+/.exec(contentType)[0];
|
||||
} else if (fileName) {
|
||||
var dotIndex = fileName.lastIndexOf('.');
|
||||
if (dotIndex == -1) {
|
||||
return 'unknown';
|
||||
}
|
||||
if (contentType) {
|
||||
return /^[^/]+/.exec(contentType)[0];
|
||||
} else if (fileName) {
|
||||
var dotIndex = fileName.lastIndexOf('.');
|
||||
if (dotIndex == -1) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
var ext = fileName.substr(dotIndex + 1);
|
||||
if (/^mp4|mov|m4v|flv|f4v$/i.test(ext)) {
|
||||
return 'video';
|
||||
} else if (/^mp3|m4a|aac|wav|flac|ogg$/i.test(ext)) {
|
||||
return 'audio';
|
||||
} else if (/^html|htm|pdf|odf|doc|docx|md|markdown|txt$/i.test(ext)) {
|
||||
return 'document';
|
||||
} else {
|
||||
return 'unknown';
|
||||
}
|
||||
} else {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
var ext = fileName.substr(dotIndex + 1);
|
||||
if (/^mp4|mov|m4v|flv|f4v$/i.test(ext)) {
|
||||
return 'video';
|
||||
} else if (/^mp3|m4a|aac|wav|flac|ogg$/i.test(ext)) {
|
||||
return 'audio';
|
||||
} else if (/^html|htm|pdf|odf|doc|docx|md|markdown|txt$/i.test(ext)) {
|
||||
return 'document';
|
||||
} else {
|
||||
return 'unknown';
|
||||
}
|
||||
} else {
|
||||
return 'unknown';
|
||||
}
|
||||
};
|
||||
|
||||
lbry.stop = function(callback) {
|
||||
lbry.call('stop', {}, callback);
|
||||
lbry.call('stop', {}, callback);
|
||||
};
|
||||
|
||||
lbry._subscribeIdCount = 0;
|
||||
|
@ -319,49 +384,58 @@ lbry._balanceSubscribeInterval = 5000;
|
|||
|
||||
lbry._balanceUpdateInterval = null;
|
||||
lbry._updateBalanceSubscribers = function() {
|
||||
lbry.wallet_balance().then(function(balance) {
|
||||
for (let callback of Object.values(lbry._balanceSubscribeCallbacks)) {
|
||||
callback(balance);
|
||||
}
|
||||
});
|
||||
lbry.wallet_balance().then(function(balance) {
|
||||
for (let callback of Object.values(lbry._balanceSubscribeCallbacks)) {
|
||||
callback(balance);
|
||||
}
|
||||
});
|
||||
|
||||
if (!lbry._balanceUpdateInterval && Object.keys(lbry._balanceSubscribeCallbacks).length) {
|
||||
lbry._balanceUpdateInterval = setInterval(() => {
|
||||
lbry._updateBalanceSubscribers();
|
||||
}, lbry._balanceSubscribeInterval);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
delete lbry._balanceSubscribeCallbacks[subscribeId];
|
||||
if (
|
||||
lbry._balanceUpdateInterval &&
|
||||
!Object.keys(lbry._balanceSubscribeCallbacks).length
|
||||
) {
|
||||
clearInterval(lbry._balanceUpdateInterval);
|
||||
}
|
||||
};
|
||||
|
||||
lbry.showMenuIfNeeded = function() {
|
||||
const showingMenu = sessionStorage.getItem('menuShown') || null;
|
||||
const chosenMenu = lbry.getClientSetting('showDeveloperMenu') ? 'developer' : 'normal';
|
||||
if (chosenMenu != showingMenu) {
|
||||
menu.showMenubar(chosenMenu == 'developer');
|
||||
}
|
||||
sessionStorage.setItem('menuShown', chosenMenu);
|
||||
const showingMenu = sessionStorage.getItem('menuShown') || null;
|
||||
const chosenMenu = lbry.getClientSetting('showDeveloperMenu')
|
||||
? 'developer'
|
||||
: 'normal';
|
||||
if (chosenMenu != showingMenu) {
|
||||
menu.showMenubar(chosenMenu == 'developer');
|
||||
}
|
||||
sessionStorage.setItem('menuShown', chosenMenu);
|
||||
};
|
||||
|
||||
lbry.getAppVersionInfo = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
ipcRenderer.once('version-info-received', (event, versionInfo) => { resolve(versionInfo) });
|
||||
ipcRenderer.send('version-info-requested');
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
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,86 +446,118 @@ 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={}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const {name, channel_name, outpoint} = params;
|
||||
lbry.file_list = function(params = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { name, channel_name, outpoint } = params;
|
||||
|
||||
/**
|
||||
/**
|
||||
* If we're searching by outpoint, check first to see if there's a matching pending publish.
|
||||
* Pending publishes use their own faux outpoints that are always unique, so we don't need
|
||||
* to check if there's a real file.
|
||||
*/
|
||||
if (outpoint) {
|
||||
const pendingPublish = getPendingPublish({outpoint});
|
||||
if (pendingPublish) {
|
||||
resolve([pendingPublishToDummyFileInfo(pendingPublish)]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (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);
|
||||
resolve([...fileInfos, ...dummyFileInfos]);
|
||||
}, reject, reject);
|
||||
});
|
||||
}
|
||||
const dummyFileInfos = lbry
|
||||
.getPendingPublishes()
|
||||
.map(pendingPublishToDummyFileInfo);
|
||||
resolve([...fileInfos, ...dummyFileInfos]);
|
||||
},
|
||||
reject,
|
||||
reject
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
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.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
|
||||
});
|
||||
}
|
||||
|
||||
const dummyClaims = lbry.getPendingPublishes().map(pendingPublishToDummyClaim);
|
||||
resolve([...claims, ...dummyClaims]);
|
||||
}, reject, reject)
|
||||
});
|
||||
}
|
||||
const dummyClaims = lbry
|
||||
.getPendingPublishes()
|
||||
.map(pendingPublishToDummyClaim);
|
||||
resolve([...claims, ...dummyClaims]);
|
||||
},
|
||||
reject,
|
||||
reject
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const claimCacheKey = 'resolve_claim_cache';
|
||||
lbry._claimCache = getSession(claimCacheKey, {});
|
||||
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")
|
||||
}
|
||||
if (params.uri && lbry._claimCache[params.uri] !== undefined) {
|
||||
resolve(lbry._claimCache[params.uri]);
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
});
|
||||
}
|
||||
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');
|
||||
}
|
||||
if (params.uri && lbry._claimCache[params.uri] !== undefined) {
|
||||
resolve(lbry._claimCache[params.uri]);
|
||||
} else {
|
||||
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
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
lbry.cancelResolve = function(params={}) {
|
||||
const xhr = lbry._resolveXhrs[params.uri]
|
||||
if (xhr && xhr.readyState > 0 && xhr.readyState < 4) {
|
||||
xhr.abort()
|
||||
}
|
||||
}
|
||||
lbry.cancelResolve = function(params = {}) {
|
||||
const xhr = lbry._resolveXhrs[params.uri];
|
||||
if (xhr && xhr.readyState > 0 && xhr.readyState < 4) {
|
||||
xhr.abort();
|
||||
}
|
||||
};
|
||||
|
||||
lbry = new Proxy(lbry, {
|
||||
get: function(target, name) {
|
||||
if (name in target) {
|
||||
return target[name];
|
||||
}
|
||||
get: function(target, name) {
|
||||
if (name in target) {
|
||||
return target[name];
|
||||
}
|
||||
|
||||
return function(params={}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
jsonrpc.call(lbry.daemonConnectionString, name, params, resolve, reject, reject);
|
||||
});
|
||||
};
|
||||
}
|
||||
return function(params = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
jsonrpc.call(
|
||||
lbry.daemonConnectionString,
|
||||
name,
|
||||
params,
|
||||
resolve,
|
||||
reject,
|
||||
reject
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export default lbry;
|
||||
|
|
301
ui/js/lbryio.js
301
ui/js/lbryio.js
|
@ -1,163 +1,206 @@
|
|||
import {getSession, setSession} from './utils.js';
|
||||
import { getSession, setSession } from './utils.js';
|
||||
import lbry from './lbry.js';
|
||||
|
||||
const querystring = require('querystring');
|
||||
|
||||
const lbryio = {
|
||||
_accessToken: getSession('accessToken'),
|
||||
_authenticationPromise: null,
|
||||
_user : null,
|
||||
enabled: true
|
||||
_accessToken: getSession('accessToken'),
|
||||
_authenticationPromise: 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) {
|
||||
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};
|
||||
resolve(rates);
|
||||
}).catch(reject);
|
||||
});
|
||||
lbryio._exchangeLastFetched = Date.now();
|
||||
}
|
||||
return lbryio._exchangePromise;
|
||||
}
|
||||
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 };
|
||||
resolve(rates);
|
||||
})
|
||||
.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
|
||||
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
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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')));
|
||||
});
|
||||
|
||||
xhr.addEventListener('timeout', function() {
|
||||
reject(new Error(__('XMLHttpRequest connection timed out')));
|
||||
});
|
||||
xhr.addEventListener('load', function() {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
|
||||
xhr.addEventListener('load', function() {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
if (!response.success) {
|
||||
if (reject) {
|
||||
let error = new Error(response.error);
|
||||
error.xhr = xhr;
|
||||
reject(error);
|
||||
} else {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent('unhandledError', {
|
||||
detail: {
|
||||
connectionString: connectionString,
|
||||
method: action,
|
||||
params: params,
|
||||
message: response.error.message,
|
||||
...(response.error.data ? { data: response.error.data } : {})
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
resolve(response.data);
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
if (reject) {
|
||||
let error = new Error(response.error);
|
||||
error.xhr = xhr;
|
||||
reject(error);
|
||||
} else {
|
||||
document.dispatchEvent(new CustomEvent('unhandledError', {
|
||||
detail: {
|
||||
connectionString: connectionString,
|
||||
method: action,
|
||||
params: params,
|
||||
message: response.error.message,
|
||||
... response.error.data ? {data: response.error.data} : {},
|
||||
}
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
resolve(response.data);
|
||||
}
|
||||
});
|
||||
// For social media auth:
|
||||
//const accessToken = localStorage.getItem('accessToken');
|
||||
//const fullParams = {...params, ... accessToken ? {access_token: accessToken} : {}};
|
||||
|
||||
// For social media auth:
|
||||
//const accessToken = localStorage.getItem('accessToken');
|
||||
//const fullParams = {...params, ... accessToken ? {access_token: accessToken} : {}};
|
||||
// Temp app ID based auth:
|
||||
const fullParams = { app_id: lbryio.getAccessToken(), ...params };
|
||||
|
||||
// Temp app ID based auth:
|
||||
const fullParams = {app_id: lbryio.getAccessToken(), ...params};
|
||||
|
||||
if (method == 'get') {
|
||||
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.send(querystring.stringify(fullParams));
|
||||
} else {
|
||||
reject(new Error(__("Invalid method")));
|
||||
}
|
||||
});
|
||||
if (method == 'get') {
|
||||
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.send(querystring.stringify(fullParams));
|
||||
} else {
|
||||
reject(new Error(__('Invalid method')));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
lbryio.getAccessToken = () => {
|
||||
const token = getSession('accessToken');
|
||||
return token ? token.toString().trim() : token;
|
||||
}
|
||||
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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
id: 1,
|
||||
has_verified_email: true
|
||||
})
|
||||
})
|
||||
}
|
||||
if (lbryio._authenticationPromise === null) {
|
||||
lbryio._authenticationPromise = new Promise((resolve, reject) => {
|
||||
lbry.status().then((response) => {
|
||||
if (!lbryio.enabled) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
id: 1,
|
||||
has_verified_email: true
|
||||
});
|
||||
});
|
||||
}
|
||||
if (lbryio._authenticationPromise === null) {
|
||||
lbryio._authenticationPromise = new Promise((resolve, reject) => {
|
||||
lbry
|
||||
.status()
|
||||
.then(response => {
|
||||
let installation_id = response.installation_id;
|
||||
|
||||
let installation_id = response.installation_id;
|
||||
function setCurrentUser() {
|
||||
lbryio
|
||||
.call('user', 'me')
|
||||
.then(data => {
|
||||
lbryio.user = data;
|
||||
resolve(data);
|
||||
})
|
||||
.catch(function(err) {
|
||||
lbryio.setAccessToken(null);
|
||||
if (!getSession('reloadedOnFailedAuth')) {
|
||||
setSession('reloadedOnFailedAuth', true);
|
||||
window.location.reload();
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setCurrentUser() {
|
||||
lbryio.call('user', 'me').then((data) => {
|
||||
lbryio.user = data
|
||||
resolve(data)
|
||||
}).catch(function(err) {
|
||||
lbryio.setAccessToken(null);
|
||||
if (!getSession('reloadedOnFailedAuth')) {
|
||||
setSession('reloadedOnFailedAuth', true)
|
||||
window.location.reload();
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!lbryio.getAccessToken()) {
|
||||
lbryio.call('user', 'new', {
|
||||
language: 'en',
|
||||
app_id: installation_id,
|
||||
}, 'post').then(function(responseData) {
|
||||
if (!responseData.id) {
|
||||
reject(new Error(__("Received invalid authentication response.")));
|
||||
}
|
||||
lbryio.setAccessToken(installation_id)
|
||||
setCurrentUser()
|
||||
}).catch(function(error) {
|
||||
/*
|
||||
if (!lbryio.getAccessToken()) {
|
||||
lbryio
|
||||
.call(
|
||||
'user',
|
||||
'new',
|
||||
{
|
||||
language: 'en',
|
||||
app_id: installation_id
|
||||
},
|
||||
'post'
|
||||
)
|
||||
.then(function(responseData) {
|
||||
if (!responseData.id) {
|
||||
reject(
|
||||
new Error(__('Received invalid authentication response.'))
|
||||
);
|
||||
}
|
||||
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()
|
||||
})
|
||||
} else {
|
||||
setCurrentUser()
|
||||
}
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
return lbryio._authenticationPromise;
|
||||
}
|
||||
lbryio.setAccessToken(installation_id);
|
||||
setCurrentUser();
|
||||
});
|
||||
} else {
|
||||
setCurrentUser();
|
||||
}
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
return lbryio._authenticationPromise;
|
||||
};
|
||||
|
||||
export default lbryio;
|
||||
|
|
315
ui/js/lbryuri.js
315
ui/js/lbryuri.js
|
@ -25,170 +25,225 @@ 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) {
|
||||
// Break into components. Empty sub-matches are converted to null
|
||||
const componentsRegex = new RegExp(
|
||||
'^((?: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);
|
||||
lbryuri.parse = function(uri, requireProto = false) {
|
||||
// Break into components. Empty sub-matches are converted to null
|
||||
const componentsRegex = new RegExp(
|
||||
'^((?: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);
|
||||
|
||||
let contentName;
|
||||
let contentName;
|
||||
|
||||
// Validate protocol
|
||||
if (requireProto && !proto) {
|
||||
throw new Error(__('LBRY URIs must include a protocol prefix (lbry://).'));
|
||||
}
|
||||
// Validate protocol
|
||||
if (requireProto && !proto) {
|
||||
throw new Error(__('LBRY URIs must include a protocol prefix (lbry://).'));
|
||||
}
|
||||
|
||||
// Validate and process name
|
||||
if (!name) {
|
||||
throw new Error(__('URI does not include name.'));
|
||||
}
|
||||
// Validate and process name
|
||||
if (!name) {
|
||||
throw new Error(__('URI does not include name.'));
|
||||
}
|
||||
|
||||
const isChannel = name.startsWith('@');
|
||||
const channelName = isChannel ? name.slice(1) : name;
|
||||
const isChannel = name.startsWith('@');
|
||||
const channelName = isChannel ? name.slice(1) : name;
|
||||
|
||||
if (isChannel) {
|
||||
if (!channelName) {
|
||||
throw new Error(__('No channel name after @.'));
|
||||
}
|
||||
if (isChannel) {
|
||||
if (!channelName) {
|
||||
throw new Error(__('No channel name after @.'));
|
||||
}
|
||||
|
||||
if (channelName.length < CHANNEL_NAME_MIN_LEN) {
|
||||
throw new Error(__(`Channel names must be at least %s characters.`, CHANNEL_NAME_MIN_LEN));
|
||||
}
|
||||
if (channelName.length < CHANNEL_NAME_MIN_LEN) {
|
||||
throw new Error(
|
||||
__(
|
||||
`Channel names must be at least %s characters.`,
|
||||
CHANNEL_NAME_MIN_LEN
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
contentName = path;
|
||||
}
|
||||
contentName = path;
|
||||
}
|
||||
|
||||
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(', ') ));
|
||||
}
|
||||
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(', ')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Validate and process modifier (claim ID, bid position or claim sequence)
|
||||
let claimId, claimSequence, bidPosition;
|
||||
if (modSep) {
|
||||
if (!modVal) {
|
||||
throw new Error(__(`No modifier provided after separator %s.`, modSep));
|
||||
}
|
||||
// Validate and process modifier (claim ID, bid position or claim sequence)
|
||||
let claimId, claimSequence, bidPosition;
|
||||
if (modSep) {
|
||||
if (!modVal) {
|
||||
throw new Error(__(`No modifier provided after separator %s.`, modSep));
|
||||
}
|
||||
|
||||
if (modSep == '#') {
|
||||
claimId = modVal;
|
||||
} else if (modSep == ':') {
|
||||
claimSequence = modVal;
|
||||
} else if (modSep == '$') {
|
||||
bidPosition = modVal;
|
||||
}
|
||||
}
|
||||
if (modSep == '#') {
|
||||
claimId = modVal;
|
||||
} else if (modSep == ':') {
|
||||
claimSequence = modVal;
|
||||
} else if (modSep == '$') {
|
||||
bidPosition = modVal;
|
||||
}
|
||||
}
|
||||
|
||||
if (claimId && (claimId.length > CLAIM_ID_MAX_LEN || !claimId.match(/^[0-9a-f]+$/))) {
|
||||
throw new Error(__(`Invalid claim ID %s.`, claimId));
|
||||
}
|
||||
if (
|
||||
claimId &&
|
||||
(claimId.length > CLAIM_ID_MAX_LEN || !claimId.match(/^[0-9a-f]+$/))
|
||||
) {
|
||||
throw new Error(__(`Invalid claim ID %s.`, claimId));
|
||||
}
|
||||
|
||||
if (claimSequence && !claimSequence.match(/^-?[1-9][0-9]*$/)) {
|
||||
throw new Error(__('Claim sequence must be a number.'));
|
||||
}
|
||||
if (claimSequence && !claimSequence.match(/^-?[1-9][0-9]*$/)) {
|
||||
throw new Error(__('Claim sequence must be a number.'));
|
||||
}
|
||||
|
||||
if (bidPosition && !bidPosition.match(/^-?[1-9][0-9]*$/)) {
|
||||
throw new Error(__('Bid position must be a number.'));
|
||||
}
|
||||
if (bidPosition && !bidPosition.match(/^-?[1-9][0-9]*$/)) {
|
||||
throw new Error(__('Bid position must be a number.'));
|
||||
}
|
||||
|
||||
// Validate and process path
|
||||
if (path) {
|
||||
if (!isChannel) {
|
||||
throw new Error(__('Only channel URIs may have a path.'));
|
||||
}
|
||||
// Validate and process path
|
||||
if (path) {
|
||||
if (!isChannel) {
|
||||
throw new Error(__('Only channel URIs may have a path.'));
|
||||
}
|
||||
|
||||
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(', ')));
|
||||
}
|
||||
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(', ')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
contentName = path;
|
||||
} else if (pathSep) {
|
||||
throw new Error(__('No path provided after /'));
|
||||
}
|
||||
contentName = path;
|
||||
} else if (pathSep) {
|
||||
throw new Error(__('No path provided after /'));
|
||||
}
|
||||
|
||||
return {
|
||||
name, path, isChannel,
|
||||
... contentName ? {contentName} : {},
|
||||
... channelName ? {channelName} : {},
|
||||
... claimSequence ? {claimSequence: parseInt(claimSequence)} : {},
|
||||
... bidPosition ? {bidPosition: parseInt(bidPosition)} : {},
|
||||
... claimId ? {claimId} : {},
|
||||
... path ? {path} : {},
|
||||
};
|
||||
}
|
||||
return {
|
||||
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;
|
||||
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.'));
|
||||
}
|
||||
}
|
||||
if (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.'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (contentName) {
|
||||
if (!name) {
|
||||
name = contentName;
|
||||
} else if (!path) {
|
||||
path = contentName;
|
||||
}
|
||||
if (path && path !== contentName) {
|
||||
throw new Error(__('Path and contentName do not match. Only one is required; most likely you wanted contentName.'));
|
||||
}
|
||||
}
|
||||
if (contentName) {
|
||||
if (!name) {
|
||||
name = contentName;
|
||||
} else if (!path) {
|
||||
path = contentName;
|
||||
}
|
||||
if (path && path !== contentName) {
|
||||
throw new Error(
|
||||
__(
|
||||
'Path and contentName do not match. Only one is required; most likely you wanted contentName.'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (includeProto ? 'lbry://' : '') + name +
|
||||
(claimId ? `#${claimId}` : '') +
|
||||
(claimSequence ? `:${claimSequence}` : '') +
|
||||
(bidPosition ? `\$${bidPosition}` : '') +
|
||||
(path ? `/${path}` : '');
|
||||
|
||||
}
|
||||
return (
|
||||
(includeProto ? 'lbry://' : '') +
|
||||
name +
|
||||
(claimId ? `#${claimId}` : '') +
|
||||
(claimSequence ? `:${claimSequence}` : '') +
|
||||
(bidPosition ? `\$${bidPosition}` : '') +
|
||||
(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
|
||||
try {
|
||||
parts = lbryuri.parse(lbryuri.normalize(uri))
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return parts && parts.name;
|
||||
}
|
||||
let parts;
|
||||
try {
|
||||
parts = lbryuri.parse(lbryuri.normalize(uri));
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return parts && parts.name;
|
||||
};
|
||||
|
||||
lbryuri.isValidName = function(name, checkCase=true) {
|
||||
const regexp = new RegExp('^[a-z0-9-]+$', checkCase ? '' : 'i');
|
||||
return regexp.test(name);
|
||||
}
|
||||
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
|
||||
try {
|
||||
parts = lbryuri.parse(lbryuri.normalize(uri))
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return parts && parts.name && !parts.claimId && !parts.bidPosition && !parts.claimSequence && !parts.isChannel && !parts.path;
|
||||
}
|
||||
let parts;
|
||||
try {
|
||||
parts = lbryuri.parse(lbryuri.normalize(uri));
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
parts &&
|
||||
parts.name &&
|
||||
!parts.claimId &&
|
||||
!parts.bidPosition &&
|
||||
!parts.claimSequence &&
|
||||
!parts.isChannel &&
|
||||
!parts.path
|
||||
);
|
||||
};
|
||||
|
||||
window.lbryuri = lbryuri;
|
||||
export default lbryuri;
|
||||
|
|
|
@ -4,9 +4,9 @@ import jsonrpc from './jsonrpc.js';
|
|||
const queryTimeout = 3000;
|
||||
const maxQueryTries = 2;
|
||||
const defaultServers = [
|
||||
'http://lighthouse7.lbry.io:50005',
|
||||
'http://lighthouse8.lbry.io:50005',
|
||||
'http://lighthouse9.lbry.io:50005',
|
||||
'http://lighthouse7.lbry.io:50005',
|
||||
'http://lighthouse8.lbry.io:50005',
|
||||
'http://lighthouse9.lbry.io:50005'
|
||||
];
|
||||
const path = '/';
|
||||
|
||||
|
@ -14,48 +14,71 @@ let server = null;
|
|||
let connectTryNum = 0;
|
||||
|
||||
function getServers() {
|
||||
return lbry.getClientSetting('useCustomLighthouseServers')
|
||||
? lbry.getClientSetting('customLighthouseServers')
|
||||
: defaultServers;
|
||||
return lbry.getClientSetting('useCustomLighthouseServers')
|
||||
? lbry.getClientSetting('customLighthouseServers')
|
||||
: defaultServers;
|
||||
}
|
||||
|
||||
function call(method, params, callback, errorCallback) {
|
||||
if (connectTryNum >= maxQueryTries) {
|
||||
errorCallback(new Error(__(`Could not connect to Lighthouse server. Last server attempted: %s`, server)));
|
||||
return;
|
||||
}
|
||||
if (connectTryNum >= maxQueryTries) {
|
||||
errorCallback(
|
||||
new Error(
|
||||
__(
|
||||
`Could not connect to Lighthouse server. Last server attempted: %s`,
|
||||
server
|
||||
)
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Set the Lighthouse server if it hasn't been set yet, if the current server is not in current
|
||||
* set of servers (most likely because of a settings change), or we're re-trying after a failed
|
||||
* query.
|
||||
*/
|
||||
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))];
|
||||
}
|
||||
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))
|
||||
];
|
||||
}
|
||||
|
||||
jsonrpc.call(server + path, method, params, (response) => {
|
||||
connectTryNum = 0;
|
||||
callback(response);
|
||||
}, (error) => {
|
||||
connectTryNum = 0;
|
||||
errorCallback(error);
|
||||
}, () => {
|
||||
connectTryNum++;
|
||||
call(method, params, callback, errorCallback);
|
||||
}, queryTimeout);
|
||||
jsonrpc.call(
|
||||
server + path,
|
||||
method,
|
||||
params,
|
||||
response => {
|
||||
connectTryNum = 0;
|
||||
callback(response);
|
||||
},
|
||||
error => {
|
||||
connectTryNum = 0;
|
||||
errorCallback(error);
|
||||
},
|
||||
() => {
|
||||
connectTryNum++;
|
||||
call(method, params, callback, errorCallback);
|
||||
},
|
||||
queryTimeout
|
||||
);
|
||||
}
|
||||
|
||||
const lighthouse = new Proxy({}, {
|
||||
get: function(target, name) {
|
||||
return function(...params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
call(name, params, resolve, reject);
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
const lighthouse = new Proxy(
|
||||
{},
|
||||
{
|
||||
get: function(target, name) {
|
||||
return function(...params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
call(name, params, resolve, reject);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default lighthouse;
|
||||
|
|
125
ui/js/main.js
125
ui/js/main.js
|
@ -8,90 +8,87 @@ 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'));
|
||||
event.preventDefault();
|
||||
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
|
||||
if (route.match(/html$/)) {
|
||||
action = doChangePath('/discover')
|
||||
} else {
|
||||
action = doChangePath(`${route}?${queryString}`)
|
||||
}
|
||||
let action;
|
||||
if (route.match(/html$/)) {
|
||||
action = doChangePath('/discover');
|
||||
} else {
|
||||
action = doChangePath(`${route}?${queryString}`);
|
||||
}
|
||||
|
||||
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(action);
|
||||
});
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
var target = event.target;
|
||||
while (target && target !== document) {
|
||||
if (target.matches('a[href^="http"]')) {
|
||||
event.preventDefault();
|
||||
shell.openExternal(target.href);
|
||||
return;
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
ipcRenderer.on('open-uri-requested', (event, uri) => {
|
||||
if (uri && uri.startsWith('lbry://')) {
|
||||
app.store.dispatch(doNavigate('/show', { uri }));
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', event => {
|
||||
var target = event.target;
|
||||
while (target && target !== document) {
|
||||
if (target.matches('a[href^="http"]')) {
|
||||
event.preventDefault();
|
||||
shell.openExternal(target.href);
|
||||
return;
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
});
|
||||
|
||||
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 = [];
|
||||
|
||||
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 = []
|
||||
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') {
|
||||
onDaemonReady();
|
||||
} else {
|
||||
ReactDOM.render(<SplashScreen onLoadDone={onDaemonReady} />, canvas);
|
||||
}
|
||||
if (window.sessionStorage.getItem('loaded') == 'y') {
|
||||
onDaemonReady();
|
||||
} else {
|
||||
ReactDOM.render(<SplashScreen onLoadDone={onDaemonReady} />, canvas);
|
||||
}
|
||||
};
|
||||
|
||||
init();
|
||||
|
|
|
@ -1,30 +1,26 @@
|
|||
import React from 'react'
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { doFetchClaimsByChannel } from 'actions/content';
|
||||
import {
|
||||
connect
|
||||
} from 'react-redux'
|
||||
import {
|
||||
doFetchClaimsByChannel
|
||||
} from 'actions/content'
|
||||
import {
|
||||
makeSelectClaimForUri,
|
||||
makeSelectClaimsInChannelForUri
|
||||
} from 'selectors/claims'
|
||||
import ChannelPage from './view'
|
||||
makeSelectClaimForUri,
|
||||
makeSelectClaimsInChannelForUri
|
||||
} from 'selectors/claims';
|
||||
import ChannelPage from './view';
|
||||
|
||||
const makeSelect = () => {
|
||||
const selectClaim = makeSelectClaimForUri(),
|
||||
selectClaimsInChannel = makeSelectClaimsInChannelForUri()
|
||||
const selectClaim = makeSelectClaimForUri(),
|
||||
selectClaimsInChannel = makeSelectClaimsInChannelForUri();
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: selectClaim(state, props),
|
||||
claimsInChannel: selectClaimsInChannel(state, props)
|
||||
})
|
||||
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);
|
||||
|
|
|
@ -1,54 +1,57 @@
|
|||
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{
|
||||
componentDidMount() {
|
||||
this.fetchClaims(this.props)
|
||||
}
|
||||
class ChannelPage extends React.Component {
|
||||
componentDidMount() {
|
||||
this.fetchClaims(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.fetchClaims(nextProps)
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.fetchClaims(nextProps);
|
||||
}
|
||||
|
||||
fetchClaims(props) {
|
||||
if (props.claimsInChannel === undefined) {
|
||||
props.fetchClaims(props.uri)
|
||||
}
|
||||
}
|
||||
fetchClaims(props) {
|
||||
if (props.claimsInChannel === undefined) {
|
||||
props.fetchClaims(props.uri);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
claimsInChannel,
|
||||
claim,
|
||||
uri
|
||||
} = this.props
|
||||
render() {
|
||||
const { claimsInChannel, claim, uri } = this.props;
|
||||
|
||||
let contentList
|
||||
if (claimsInChannel === undefined) {
|
||||
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>
|
||||
}
|
||||
let contentList;
|
||||
if (claimsInChannel === undefined) {
|
||||
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>;
|
||||
}
|
||||
|
||||
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.")}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<h3 className="card-row__header">{__("Published Content")}</h3>
|
||||
{contentList}
|
||||
</main>
|
||||
}
|
||||
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.')}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<h3 className="card-row__header">{__('Published Content')}</h3>
|
||||
{contentList}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChannelPage;
|
||||
|
|
|
@ -1,94 +1,144 @@
|
|||
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) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showDeveloperMenu: lbry.getClientSetting('showDeveloperMenu'),
|
||||
useCustomLighthouseServers: lbry.getClientSetting('useCustomLighthouseServers'),
|
||||
customLighthouseServers: lbry.getClientSetting('customLighthouseServers').join('\n'),
|
||||
upgradePath: '',
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
showDeveloperMenu: lbry.getClientSetting('showDeveloperMenu'),
|
||||
useCustomLighthouseServers: lbry.getClientSetting(
|
||||
'useCustomLighthouseServers'
|
||||
),
|
||||
customLighthouseServers: lbry
|
||||
.getClientSetting('customLighthouseServers')
|
||||
.join('\n'),
|
||||
upgradePath: ''
|
||||
};
|
||||
}
|
||||
|
||||
handleShowDeveloperMenuChange(event) {
|
||||
lbry.setClientSetting('showDeveloperMenu', event.target.checked);
|
||||
lbry.showMenuIfNeeded();
|
||||
this.setState({
|
||||
showDeveloperMenu: event.target.checked,
|
||||
});
|
||||
}
|
||||
handleShowDeveloperMenuChange(event) {
|
||||
lbry.setClientSetting('showDeveloperMenu', event.target.checked);
|
||||
lbry.showMenuIfNeeded();
|
||||
this.setState({
|
||||
showDeveloperMenu: event.target.checked
|
||||
});
|
||||
}
|
||||
|
||||
handleUseCustomLighthouseServersChange(event) {
|
||||
lbry.setClientSetting('useCustomLighthouseServers', event.target.checked);
|
||||
this.setState({
|
||||
useCustomLighthouseServers: event.target.checked,
|
||||
});
|
||||
}
|
||||
handleUseCustomLighthouseServersChange(event) {
|
||||
lbry.setClientSetting('useCustomLighthouseServers', event.target.checked);
|
||||
this.setState({
|
||||
useCustomLighthouseServers: event.target.checked
|
||||
});
|
||||
}
|
||||
|
||||
handleUpgradeFileChange(event) {
|
||||
this.setState({
|
||||
upgradePath: event.target.value,
|
||||
});
|
||||
}
|
||||
handleUpgradeFileChange(event) {
|
||||
this.setState({
|
||||
upgradePath: event.target.value
|
||||
});
|
||||
}
|
||||
|
||||
handleForceUpgradeClick() {
|
||||
let upgradeSent = false;
|
||||
if (!this.state.upgradePath) {
|
||||
alert(__('Please select a file to upgrade from'));
|
||||
} else {
|
||||
try {
|
||||
const stats = fs.lstatSync(this.state.upgradePath);
|
||||
if (stats.isFile()) {
|
||||
console.log('Starting upgrade using ' + this.state.upgradePath);
|
||||
ipcRenderer.send('upgrade', this.state.upgradePath);
|
||||
upgradeSent = true;
|
||||
}
|
||||
}
|
||||
catch (e) {}
|
||||
if (!upgradeSent) {
|
||||
alert('Failed to start upgrade. Is "' + this.state.upgradePath + '" a valid path to the upgrade?');
|
||||
}
|
||||
}
|
||||
}
|
||||
handleForceUpgradeClick() {
|
||||
let upgradeSent = false;
|
||||
if (!this.state.upgradePath) {
|
||||
alert(__('Please select a file to upgrade from'));
|
||||
} else {
|
||||
try {
|
||||
const stats = fs.lstatSync(this.state.upgradePath);
|
||||
if (stats.isFile()) {
|
||||
console.log('Starting upgrade using ' + this.state.upgradePath);
|
||||
ipcRenderer.send('upgrade', this.state.upgradePath);
|
||||
upgradeSent = true;
|
||||
}
|
||||
} catch (e) {}
|
||||
if (!upgradeSent) {
|
||||
alert(
|
||||
'Failed to start upgrade. Is "' +
|
||||
this.state.upgradePath +
|
||||
'" a valid path to the upgrade?'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<main>
|
||||
<section className="card">
|
||||
<h3>{__("Developer Settings")}</h3>
|
||||
<div className="form-row">
|
||||
<label><FormField type="checkbox" onChange={(event) => { this.handleShowDeveloperMenuChange() }} checked={this.state.showDeveloperMenu} /> {__("Show developer menu")}</label>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<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>
|
||||
</label>
|
||||
</div>
|
||||
: null}
|
||||
</section>
|
||||
<section className="card">
|
||||
<div className="form-row">
|
||||
<FormField name="file" ref="file" type="file" onChange={(event) => { this.handleUpgradeFileChange() }}/>
|
||||
|
||||
<Link label={__("Force Upgrade")} button="alt" onClick={(event) => { this.handleForceUpgradeClick() }} />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<main>
|
||||
<section className="card">
|
||||
<h3>{__('Developer Settings')}</h3>
|
||||
<div className="form-row">
|
||||
<label>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
onChange={event => {
|
||||
this.handleShowDeveloperMenuChange();
|
||||
}}
|
||||
checked={this.state.showDeveloperMenu}
|
||||
/>
|
||||
{' '}
|
||||
{__('Show developer menu')}
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<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>
|
||||
</label>
|
||||
</div>
|
||||
: null}
|
||||
</section>
|
||||
<section className="card">
|
||||
<div className="form-row">
|
||||
<FormField
|
||||
name="file"
|
||||
ref="file"
|
||||
type="file"
|
||||
onChange={event => {
|
||||
this.handleUpgradeFileChange();
|
||||
}}
|
||||
/>
|
||||
|
||||
<Link
|
||||
label={__('Force Upgrade')}
|
||||
button="alt"
|
||||
onClick={event => {
|
||||
this.handleForceUpgradeClick();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DeveloperPage;
|
||||
|
|
|
@ -1,23 +1,19 @@
|
|||
import React from 'react'
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { doFetchFeaturedUris } from 'actions/content';
|
||||
import {
|
||||
connect
|
||||
} from 'react-redux'
|
||||
import {
|
||||
doFetchFeaturedUris,
|
||||
} from 'actions/content'
|
||||
import {
|
||||
selectFeaturedUris,
|
||||
selectFetchingFeaturedUris,
|
||||
} from 'selectors/content'
|
||||
import DiscoverPage from './view'
|
||||
selectFeaturedUris,
|
||||
selectFetchingFeaturedUris
|
||||
} from 'selectors/content';
|
||||
import DiscoverPage from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
featuredUris: selectFeaturedUris(state),
|
||||
fetchingFeaturedUris: selectFetchingFeaturedUris(state),
|
||||
})
|
||||
const select = state => ({
|
||||
featuredUris: selectFeaturedUris(state),
|
||||
fetchingFeaturedUris: selectFetchingFeaturedUris(state)
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
fetchFeaturedUris: () => dispatch(doFetchFeaturedUris())
|
||||
})
|
||||
const perform = dispatch => ({
|
||||
fetchFeaturedUris: () => dispatch(doFetchFeaturedUris())
|
||||
});
|
||||
|
||||
export default connect(select, perform)(DiscoverPage)
|
||||
export default connect(select, perform)(DiscoverPage);
|
||||
|
|
|
@ -1,62 +1,74 @@
|
|||
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 ' +
|
||||
'rest of the LBRY community. Bid on the names "one," "two," "three," "four" and ' +
|
||||
'"five" to put your content here!');
|
||||
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!';
|
||||
|
||||
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" />}
|
||||
</h3>
|
||||
{names && names.map(name => <FileCard key={name} displayStyle="card" uri={lbryuri.normalize(name)} />)}
|
||||
</div>
|
||||
}
|
||||
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)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
class DiscoverPage extends React.Component{
|
||||
componentWillMount() {
|
||||
this.props.fetchFeaturedUris()
|
||||
}
|
||||
class DiscoverPage extends React.Component {
|
||||
componentWillMount() {
|
||||
this.props.fetchFeaturedUris();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
featuredUris,
|
||||
fetchingFeaturedUris,
|
||||
} = this.props
|
||||
const failedToLoad = !fetchingFeaturedUris && (
|
||||
featuredUris === undefined ||
|
||||
(featuredUris !== undefined && Object.keys(featuredUris).length === 0)
|
||||
)
|
||||
render() {
|
||||
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>
|
||||
}
|
||||
</main>
|
||||
)
|
||||
}
|
||||
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>}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DiscoverPage;
|
||||
|
|
|
@ -1,27 +1,21 @@
|
|||
import React from 'react'
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { doFetchFileInfosAndPublishedClaims } from 'actions/file_info';
|
||||
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'
|
||||
selectFileInfosDownloaded,
|
||||
selectFileListDownloadedOrPublishedIsPending
|
||||
} from 'selectors/file_info';
|
||||
import { doNavigate } from 'actions/app';
|
||||
import FileListDownloaded from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
fileInfos: selectFileInfosDownloaded(state),
|
||||
isPending: selectFileListDownloadedOrPublishedIsPending(state),
|
||||
})
|
||||
const select = state => ({
|
||||
fileInfos: selectFileInfosDownloaded(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);
|
||||
|
|
|
@ -2,44 +2,48 @@ 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()
|
||||
}
|
||||
componentWillMount() {
|
||||
this.props.fetchFileInfosDownloaded();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
fileInfos,
|
||||
isPending,
|
||||
navigate,
|
||||
} = this.props
|
||||
render() {
|
||||
const { fileInfos, isPending, navigate } = this.props;
|
||||
|
||||
let content
|
||||
if (fileInfos && fileInfos.length > 0) {
|
||||
content = <FileList fileInfos={fileInfos} fetching={isPending} />
|
||||
} else {
|
||||
if (isPending) {
|
||||
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>
|
||||
}
|
||||
}
|
||||
let content;
|
||||
if (fileInfos && fileInfos.length > 0) {
|
||||
content = <FileList fileInfos={fileInfos} fetching={isPending} />;
|
||||
} else {
|
||||
if (isPending) {
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<SubHeader />
|
||||
{content}
|
||||
</main>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<SubHeader />
|
||||
{content}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FileListDownloaded
|
||||
export default FileListDownloaded;
|
||||
|
|
|
@ -1,27 +1,21 @@
|
|||
import React from 'react'
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { doFetchFileInfosAndPublishedClaims } from 'actions/file_info';
|
||||
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'
|
||||
selectFileInfosPublished,
|
||||
selectFileListDownloadedOrPublishedIsPending
|
||||
} from 'selectors/file_info';
|
||||
import { doNavigate } from 'actions/app';
|
||||
import FileListPublished from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
fileInfos: selectFileInfosPublished(state),
|
||||
isPending: selectFileListDownloadedOrPublishedIsPending(state),
|
||||
})
|
||||
const select = state => ({
|
||||
fileInfos: selectFileInfosPublished(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);
|
||||
|
|
|
@ -2,66 +2,76 @@ 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()
|
||||
}
|
||||
componentWillMount() {
|
||||
this.props.fetchFileListPublished();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if(this.props.fileInfos.length > 0) this._requestPublishReward()
|
||||
}
|
||||
componentDidUpdate() {
|
||||
if (this.props.fileInfos.length > 0) this._requestPublishReward();
|
||||
}
|
||||
|
||||
_requestPublishReward() {
|
||||
// TODO this is throwing an error now
|
||||
// Error: LBRY internal API is disabled
|
||||
//
|
||||
// lbryio.call('reward', 'list', {}).then(function(userRewards) {
|
||||
// //already rewarded
|
||||
// if (userRewards.filter(function (reward) {
|
||||
// return reward.reward_type == rewards.TYPE_FIRST_PUBLISH && reward.transaction_id
|
||||
// }).length) {
|
||||
// return
|
||||
// }
|
||||
// else {
|
||||
// rewards.claimReward(rewards.TYPE_FIRST_PUBLISH).catch(() => {})
|
||||
// }
|
||||
// })
|
||||
}
|
||||
_requestPublishReward() {
|
||||
// TODO this is throwing an error now
|
||||
// Error: LBRY internal API is disabled
|
||||
//
|
||||
// lbryio.call('reward', 'list', {}).then(function(userRewards) {
|
||||
// //already rewarded
|
||||
// if (userRewards.filter(function (reward) {
|
||||
// return reward.reward_type == rewards.TYPE_FIRST_PUBLISH && reward.transaction_id
|
||||
// }).length) {
|
||||
// return
|
||||
// }
|
||||
// else {
|
||||
// rewards.claimReward(rewards.TYPE_FIRST_PUBLISH).catch(() => {})
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
fileInfos,
|
||||
isPending,
|
||||
navigate,
|
||||
} = this.props
|
||||
render() {
|
||||
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} />
|
||||
} else {
|
||||
if (isPending) {
|
||||
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>
|
||||
}
|
||||
}
|
||||
if (fileInfos && fileInfos.length > 0) {
|
||||
content = (
|
||||
<FileList
|
||||
fileInfos={fileInfos}
|
||||
fetching={isPending}
|
||||
fileTileShowEmpty={FileTile.SHOW_EMPTY_PENDING}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
if (isPending) {
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<SubHeader />
|
||||
{content}
|
||||
</main>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<SubHeader />
|
||||
{content}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FileListPublished
|
||||
export default FileListPublished;
|
||||
|
|
|
@ -1,51 +1,39 @@
|
|||
import React from 'react'
|
||||
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 {
|
||||
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'
|
||||
makeSelectClaimForUri,
|
||||
makeSelectContentTypeForUri,
|
||||
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()
|
||||
const selectClaim = makeSelectClaimForUri(),
|
||||
selectContentType = makeSelectContentTypeForUri(),
|
||||
selectFileInfo = makeSelectFileInfoForUri(),
|
||||
selectCostInfo = makeSelectCostInfoForUri(),
|
||||
selectMetadata = makeSelectMetadataForUri();
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: selectClaim(state, props),
|
||||
contentType: selectContentType(state, props),
|
||||
costInfo: selectCostInfo(state, props),
|
||||
metadata: selectMetadata(state, props),
|
||||
fileInfo: selectFileInfo(state, props)
|
||||
})
|
||||
const select = (state, props) => ({
|
||||
claim: selectClaim(state, props),
|
||||
contentType: selectContentType(state, props),
|
||||
costInfo: selectCostInfo(state, props),
|
||||
metadata: selectMetadata(state, props),
|
||||
fileInfo: selectFileInfo(state, props)
|
||||
});
|
||||
|
||||
return select
|
||||
}
|
||||
return select;
|
||||
};
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||
fetchFileInfo: (uri) => dispatch(doFetchFileInfo(uri)),
|
||||
fetchCostInfo: (uri) => dispatch(doFetchCostInfoForUri(uri)),
|
||||
})
|
||||
const perform = dispatch => ({
|
||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
||||
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri))
|
||||
});
|
||||
|
||||
export default connect(makeSelect, perform)(FilePage)
|
||||
export default connect(makeSelect, perform)(FilePage);
|
||||
|
|
|
@ -1,139 +1,145 @@
|
|||
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);
|
||||
const mediaType = lbry.getMediaType(contentType);
|
||||
|
||||
return (
|
||||
<table className="table-standard">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{__("Content-Type")}</td><td>{mediaType}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{__("Author")}</td><td>{author}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{__("Language")}</td><td>{language}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{__("License")}</td><td>{license}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<table className="table-standard">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{__('Content-Type')}</td><td>{mediaType}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{__('Author')}</td><td>{author}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{__('Language')}</td><td>{language}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<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);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fetchFileInfo(this.props)
|
||||
this.fetchCostInfo(this.props)
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.fetchFileInfo(nextProps);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.fetchFileInfo(nextProps)
|
||||
}
|
||||
fetchFileInfo(props) {
|
||||
if (props.fileInfo === undefined) {
|
||||
props.fetchFileInfo(props.uri);
|
||||
}
|
||||
}
|
||||
|
||||
fetchFileInfo(props) {
|
||||
if (props.fileInfo === undefined) {
|
||||
props.fetchFileInfo(props.uri)
|
||||
}
|
||||
}
|
||||
fetchCostInfo(props) {
|
||||
if (props.costInfo === undefined) {
|
||||
props.fetchCostInfo(props.uri);
|
||||
}
|
||||
}
|
||||
|
||||
fetchCostInfo(props) {
|
||||
if (props.costInfo === undefined) {
|
||||
props.fetchCostInfo(props.uri)
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const { claim, fileInfo, metadata, contentType, uri } = this.props;
|
||||
|
||||
render() {
|
||||
const {
|
||||
claim,
|
||||
fileInfo,
|
||||
metadata,
|
||||
contentType,
|
||||
uri,
|
||||
} = this.props
|
||||
if (!claim || !metadata) {
|
||||
return (
|
||||
<span className="empty">{__('Empty claim or metadata info.')}</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (!claim || !metadata) {
|
||||
return <span className="empty">{__("Empty claim or metadata info.")}</span>
|
||||
}
|
||||
const {
|
||||
txid,
|
||||
nout,
|
||||
channel_name: channelName,
|
||||
has_signature: hasSignature,
|
||||
signature_is_valid: signatureIsValid,
|
||||
value
|
||||
} = claim;
|
||||
|
||||
const {
|
||||
txid,
|
||||
nout,
|
||||
channel_name: channelName,
|
||||
has_signature: hasSignature,
|
||||
signature_is_valid: signatureIsValid,
|
||||
value
|
||||
} = 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 />) }
|
||||
</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>
|
||||
<div className="card__subtitle">
|
||||
{ channelUri ?
|
||||
<Link onClick={() => this.props.navigate('/show', { uri: channelUri })}>{uriIndicator}</Link> :
|
||||
uriIndicator}
|
||||
</div>
|
||||
<div className="card__actions">
|
||||
<FileActions uri={uri} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="card__content card__subtext card__subtext card__subtext--allow-newlines">
|
||||
{metadata && metadata.description}
|
||||
</div>
|
||||
</div>
|
||||
{ metadata ?
|
||||
<div className="card__content">
|
||||
<FormatItem metadata={metadata} contentType={contentType} />
|
||||
</div> : '' }
|
||||
<div className="card__content">
|
||||
<Link href="https://lbry.io/dmca" label={__("report")} className="button-text-help" />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
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 />}
|
||||
</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>
|
||||
<div className="card__subtitle">
|
||||
{channelUri
|
||||
? <Link
|
||||
onClick={() =>
|
||||
this.props.navigate('/show', { uri: channelUri })}
|
||||
>
|
||||
{uriIndicator}
|
||||
</Link>
|
||||
: uriIndicator}
|
||||
</div>
|
||||
<div className="card__actions">
|
||||
<FileActions uri={uri} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="card__content card__subtext card__subtext card__subtext--allow-newlines">
|
||||
{metadata && metadata.description}
|
||||
</div>
|
||||
</div>
|
||||
{metadata
|
||||
? <div className="card__content">
|
||||
<FormatItem metadata={metadata} contentType={contentType} />
|
||||
</div>
|
||||
: ''}
|
||||
<div className="card__content">
|
||||
<Link
|
||||
href="https://lbry.io/dmca"
|
||||
label={__('report')}
|
||||
className="button-text-help"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FilePage;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -2,137 +2,165 @@
|
|||
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) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
versionInfo: null,
|
||||
lbryId: null,
|
||||
uiVersion: null,
|
||||
upgradeAvailable: null
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
versionInfo: null,
|
||||
lbryId: null,
|
||||
uiVersion: null,
|
||||
upgradeAvailable: null
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
lbry.getAppVersionInfo().then(({remoteVersion, upgradeAvailable}) => {
|
||||
this.setState({
|
||||
uiVersion: remoteVersion,
|
||||
upgradeAvailable: upgradeAvailable
|
||||
});
|
||||
});
|
||||
lbry.call('version', {}, (info) => {
|
||||
this.setState({
|
||||
versionInfo: info
|
||||
})
|
||||
})
|
||||
lbry.getSessionInfo((info) => {
|
||||
this.setState({
|
||||
lbryId: info.lbry_id,
|
||||
});
|
||||
});
|
||||
}
|
||||
componentWillMount() {
|
||||
lbry.getAppVersionInfo().then(({ remoteVersion, upgradeAvailable }) => {
|
||||
this.setState({
|
||||
uiVersion: remoteVersion,
|
||||
upgradeAvailable: upgradeAvailable
|
||||
});
|
||||
});
|
||||
lbry.call('version', {}, info => {
|
||||
this.setState({
|
||||
versionInfo: info
|
||||
});
|
||||
});
|
||||
lbry.getSessionInfo(info => {
|
||||
this.setState({
|
||||
lbryId: info.lbry_id
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let ver, osName, platform, newVerLink;
|
||||
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');
|
||||
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';
|
||||
|
||||
platform = `${osName} ${ver.os_release}`
|
||||
newVerLink = 'https://lbry.io/get/lbry.dmg';
|
||||
} else if (ver.os_system == 'Linux') {
|
||||
platform = `Linux (${ver.platform})`;
|
||||
newVerLink = 'https://lbry.io/get/lbry.deb';
|
||||
} else {
|
||||
platform = `Windows (${ver.platform})`;
|
||||
newVerLink = 'https://lbry.io/get/lbry.msi';
|
||||
}
|
||||
} else {
|
||||
ver = null;
|
||||
}
|
||||
platform = `${osName} ${ver.os_release}`;
|
||||
newVerLink = 'https://lbry.io/get/lbry.dmg';
|
||||
} else if (ver.os_system == 'Linux') {
|
||||
platform = `Linux (${ver.platform})`;
|
||||
newVerLink = 'https://lbry.io/get/lbry.deb';
|
||||
} else {
|
||||
platform = `Windows (${ver.platform})`;
|
||||
newVerLink = 'https://lbry.io/get/lbry.msi';
|
||||
}
|
||||
} else {
|
||||
ver = null;
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<SubHeader />
|
||||
<section className="card">
|
||||
<div className="card__title-primary">
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
<section className="card">
|
||||
<div className="card__title-primary">
|
||||
<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.")}
|
||||
</p>
|
||||
<p>
|
||||
<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__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>
|
||||
</div>
|
||||
</section>
|
||||
<section className="card">
|
||||
<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">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{__("daemon (lbrynet)")}</th>
|
||||
<td>{ver.lbrynet_version}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{__("wallet (lbryum)")}</th>
|
||||
<td>{ver.lbryum_version}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{__("interface")}</th>
|
||||
<td>{this.state.uiVersion}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{__("Platform")}</th>
|
||||
<td>{platform}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{__("Installation ID")}</th>
|
||||
<td>{this.state.lbryId}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table> :
|
||||
<BusyMessage message={__("Looking up version info")} />
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<SubHeader />
|
||||
<section className="card">
|
||||
<div className="card__title-primary">
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
<section className="card">
|
||||
<div className="card__title-primary">
|
||||
<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.')}
|
||||
</p>
|
||||
<p>
|
||||
<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__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>
|
||||
</div>
|
||||
</section>
|
||||
<section className="card">
|
||||
<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">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{__('daemon (lbrynet)')}</th>
|
||||
<td>{ver.lbrynet_version}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{__('wallet (lbryum)')}</th>
|
||||
<td>{ver.lbryum_version}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{__('interface')}</th>
|
||||
<td>{this.state.uiVersion}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{__('Platform')}</th>
|
||||
<td>{platform}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{__('Installation ID')}</th>
|
||||
<td>{this.state.lbryId}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
: <BusyMessage message={__('Looking up version info')} />}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default HelpPage;
|
||||
|
|
|
@ -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) => ({
|
||||
myClaims: selectMyClaims(state)
|
||||
})
|
||||
const select = state => ({
|
||||
myClaims: selectMyClaims(state)
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
back: () => dispatch(doHistoryBack()),
|
||||
navigate: (path) => dispatch(doNavigate(path)),
|
||||
})
|
||||
const perform = dispatch => ({
|
||||
back: () => dispatch(doHistoryBack()),
|
||||
navigate: path => dispatch(doNavigate(path))
|
||||
});
|
||||
|
||||
export default connect(select, perform)(PublishPage)
|
||||
export default connect(select, perform)(PublishPage);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,68 +1,101 @@
|
|||
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';
|
||||
|
||||
class ReportPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
submitting: false,
|
||||
modal: null,
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
submitting: false,
|
||||
modal: null
|
||||
};
|
||||
}
|
||||
|
||||
submitMessage() {
|
||||
if (this._messageArea.value) {
|
||||
this.setState({
|
||||
submitting: true
|
||||
});
|
||||
lbry.reportBug(this._messageArea.value, () => {
|
||||
this.setState({
|
||||
submitting: false,
|
||||
modal: 'submitted',
|
||||
});
|
||||
});
|
||||
this._messageArea.value = '';
|
||||
}
|
||||
}
|
||||
submitMessage() {
|
||||
if (this._messageArea.value) {
|
||||
this.setState({
|
||||
submitting: true
|
||||
});
|
||||
lbry.reportBug(this._messageArea.value, () => {
|
||||
this.setState({
|
||||
submitting: false,
|
||||
modal: 'submitted'
|
||||
});
|
||||
});
|
||||
this._messageArea.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
closeModal() {
|
||||
this.setState({
|
||||
modal: null,
|
||||
})
|
||||
}
|
||||
closeModal() {
|
||||
this.setState({
|
||||
modal: null
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<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>
|
||||
<div className="form-row">
|
||||
<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>
|
||||
</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")}/>.
|
||||
</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>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<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>
|
||||
<div className="form-row">
|
||||
<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>
|
||||
</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')}
|
||||
/>.
|
||||
</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>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ReportPage;
|
||||
|
|
|
@ -1,77 +1,100 @@
|
|||
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 = {
|
||||
type: React.PropTypes.string.isRequired,
|
||||
title: React.PropTypes.string.isRequired,
|
||||
description: React.PropTypes.string.isRequired,
|
||||
claimed: React.PropTypes.bool.isRequired,
|
||||
value: React.PropTypes.number.isRequired,
|
||||
onRewardClaim: React.PropTypes.func
|
||||
}
|
||||
static propTypes = {
|
||||
type: React.PropTypes.string.isRequired,
|
||||
title: React.PropTypes.string.isRequired,
|
||||
description: React.PropTypes.string.isRequired,
|
||||
claimed: React.PropTypes.bool.isRequired,
|
||||
value: React.PropTypes.number.isRequired,
|
||||
onRewardClaim: React.PropTypes.func
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<section className="card">
|
||||
<div className="card__inner">
|
||||
<div className="card__title-primary">
|
||||
<CreditAmount amount={this.props.value} />
|
||||
<h3>{this.props.title}</h3>
|
||||
</div>
|
||||
<div className="card__actions">
|
||||
{this.props.claimed
|
||||
? <span><Icon icon="icon-check" /> {__("Reward claimed.")}</span>
|
||||
: <RewardLink {...this.props} />}
|
||||
</div>
|
||||
<div className="card__content">{this.props.description}</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<section className="card">
|
||||
<div className="card__inner">
|
||||
<div className="card__title-primary">
|
||||
<CreditAmount amount={this.props.value} />
|
||||
<h3>{this.props.title}</h3>
|
||||
</div>
|
||||
<div className="card__actions">
|
||||
{this.props.claimed
|
||||
? <span><Icon icon="icon-check" /> {__('Reward claimed.')}</span>
|
||||
: <RewardLink {...this.props} />}
|
||||
</div>
|
||||
<div className="card__content">{this.props.description}</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class RewardsPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
userRewards: null,
|
||||
failed: null,
|
||||
};
|
||||
}
|
||||
this.state = {
|
||||
userRewards: null,
|
||||
failed: null
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.loadRewards()
|
||||
}
|
||||
componentWillMount() {
|
||||
this.loadRewards();
|
||||
}
|
||||
|
||||
loadRewards() {
|
||||
lbryio.call('reward', 'list', {}).then((userRewards) => {
|
||||
this.setState({
|
||||
userRewards: userRewards,
|
||||
});
|
||||
}, () => {
|
||||
this.setState({failed: true })
|
||||
});
|
||||
}
|
||||
loadRewards() {
|
||||
lbryio.call('reward', 'list', {}).then(
|
||||
userRewards => {
|
||||
this.setState({
|
||||
userRewards: userRewards
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.setState({ failed: true });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<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} />;
|
||||
})}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RewardsPage;
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
import React from 'react'
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
connect,
|
||||
} from 'react-redux'
|
||||
import {
|
||||
selectIsSearching,
|
||||
selectSearchQuery,
|
||||
selectCurrentSearchResults,
|
||||
} from 'selectors/search'
|
||||
import {
|
||||
doNavigate,
|
||||
} from 'actions/app'
|
||||
import SearchPage from './view'
|
||||
selectIsSearching,
|
||||
selectSearchQuery,
|
||||
selectCurrentSearchResults
|
||||
} from 'selectors/search';
|
||||
import { doNavigate } from 'actions/app';
|
||||
import SearchPage from './view';
|
||||
|
||||
const select = (state) => ({
|
||||
isSearching: selectIsSearching(state),
|
||||
query: selectSearchQuery(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);
|
||||
|
|
|
@ -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 {
|
||||
render() {
|
||||
const { query } = this.props;
|
||||
|
||||
class SearchPage extends React.Component{
|
||||
render() {
|
||||
const {
|
||||
query,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
{ 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" />
|
||||
</h3>
|
||||
<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" />
|
||||
</h3>
|
||||
<FileListSearch query={query} />
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
{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"
|
||||
/>
|
||||
</h3>
|
||||
<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"
|
||||
/>
|
||||
</h3>
|
||||
<FileListSearch query={query} />
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default SearchPage;
|
||||
|
|
|
@ -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) => ({
|
||||
daemonSettings: selectDaemonSettings(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);
|
||||
|
|
|
@ -1,94 +1,93 @@
|
|||
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);
|
||||
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'),
|
||||
}
|
||||
}
|
||||
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')
|
||||
};
|
||||
}
|
||||
|
||||
setDaemonSetting(name, value) {
|
||||
this.props.setDaemonSetting(name, value)
|
||||
}
|
||||
setDaemonSetting(name, value) {
|
||||
this.props.setDaemonSetting(name, value);
|
||||
}
|
||||
|
||||
setClientSetting(name, value) {
|
||||
lbry.setClientSetting(name, value)
|
||||
this._onSettingSaveSuccess()
|
||||
}
|
||||
setClientSetting(name, value) {
|
||||
lbry.setClientSetting(name, value);
|
||||
this._onSettingSaveSuccess();
|
||||
}
|
||||
|
||||
onRunOnStartChange(event) {
|
||||
this.setDaemonSetting('run_on_startup', event.target.checked);
|
||||
}
|
||||
onRunOnStartChange(event) {
|
||||
this.setDaemonSetting('run_on_startup', event.target.checked);
|
||||
}
|
||||
|
||||
onShareDataChange(event) {
|
||||
this.setDaemonSetting('share_usage_data', event.target.checked);
|
||||
}
|
||||
onShareDataChange(event) {
|
||||
this.setDaemonSetting('share_usage_data', event.target.checked);
|
||||
}
|
||||
|
||||
onDownloadDirChange(event) {
|
||||
this.setDaemonSetting('download_directory', event.target.value);
|
||||
}
|
||||
onDownloadDirChange(event) {
|
||||
this.setDaemonSetting('download_directory', event.target.value);
|
||||
}
|
||||
|
||||
onMaxUploadPrefChange(isLimited) {
|
||||
if (!isLimited) {
|
||||
this.setDaemonSetting('max_upload', 0.0);
|
||||
}
|
||||
this.setState({
|
||||
isMaxUpload: isLimited
|
||||
});
|
||||
}
|
||||
onMaxUploadPrefChange(isLimited) {
|
||||
if (!isLimited) {
|
||||
this.setDaemonSetting('max_upload', 0.0);
|
||||
}
|
||||
this.setState({
|
||||
isMaxUpload: isLimited
|
||||
});
|
||||
}
|
||||
|
||||
onMaxUploadFieldChange(event) {
|
||||
this.setDaemonSetting('max_upload', Number(event.target.value));
|
||||
}
|
||||
onMaxUploadFieldChange(event) {
|
||||
this.setDaemonSetting('max_upload', Number(event.target.value));
|
||||
}
|
||||
|
||||
onMaxDownloadPrefChange(isLimited) {
|
||||
if (!isLimited) {
|
||||
this.setDaemonSetting('max_download', 0.0);
|
||||
}
|
||||
this.setState({
|
||||
isMaxDownload: isLimited
|
||||
});
|
||||
}
|
||||
onMaxDownloadPrefChange(isLimited) {
|
||||
if (!isLimited) {
|
||||
this.setDaemonSetting('max_download', 0.0);
|
||||
}
|
||||
this.setState({
|
||||
isMaxDownload: isLimited
|
||||
});
|
||||
}
|
||||
|
||||
onMaxDownloadFieldChange(event) {
|
||||
this.setDaemonSetting('max_download', Number(event.target.value));
|
||||
}
|
||||
onMaxDownloadFieldChange(event) {
|
||||
this.setDaemonSetting('max_download', Number(event.target.value));
|
||||
}
|
||||
|
||||
onShowNsfwChange(event) {
|
||||
lbry.setClientSetting('showNsfw', event.target.checked);
|
||||
}
|
||||
onShowNsfwChange(event) {
|
||||
lbry.setClientSetting('showNsfw', event.target.checked);
|
||||
}
|
||||
|
||||
// onLanguageChange(language) {
|
||||
// lbry.setClientSetting('language', language);
|
||||
// i18n.setLocale(language);
|
||||
// this.setState({language: language})
|
||||
// }
|
||||
// onLanguageChange(language) {
|
||||
// lbry.setClientSetting('language', language);
|
||||
// i18n.setLocale(language);
|
||||
// this.setState({language: language})
|
||||
// }
|
||||
|
||||
onShowUnavailableChange(event) {
|
||||
onShowUnavailableChange(event) {}
|
||||
|
||||
}
|
||||
render() {
|
||||
const { daemonSettings } = this.props;
|
||||
|
||||
render() {
|
||||
const {
|
||||
daemonSettings
|
||||
} = this.props
|
||||
|
||||
if (!daemonSettings) {
|
||||
return <main className="main--single-column"><span className="empty">{__("Failed to load settings.")}</span></main>;
|
||||
}
|
||||
/*
|
||||
if (!daemonSettings) {
|
||||
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>
|
||||
|
@ -101,100 +100,136 @@ class SettingsPage extends React.Component {
|
|||
</div>
|
||||
</section>
|
||||
*/
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<SubHeader />
|
||||
<section className="card">
|
||||
<div className="card__content">
|
||||
<h3>{__("Download Directory")}</h3>
|
||||
</div>
|
||||
<div className="card__content">
|
||||
<FormRow type="directory"
|
||||
name="download_directory"
|
||||
defaultValue={daemonSettings.download_directory}
|
||||
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>
|
||||
</div>
|
||||
<div className="card__content">
|
||||
<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) }}
|
||||
defaultChecked={!this.state.isMaxUpload}
|
||||
label={__("Unlimited")} />
|
||||
<div className="form-row">
|
||||
<FormField type="radio"
|
||||
name="max_upload_pref"
|
||||
onChange={() => { this.onMaxUploadPrefChange(true) }}
|
||||
defaultChecked={this.state.isMaxUpload}
|
||||
label={ this.state.isMaxUpload ? __("Up to") : __("Choose limit...") } />
|
||||
{ this.state.isMaxUpload ?
|
||||
<FormField type="number"
|
||||
min="0"
|
||||
step=".5"
|
||||
defaultValue={daemonSettings.max_upload}
|
||||
placeholder="10"
|
||||
className="form-field__input--inline"
|
||||
onChange={this.onMaxUploadFieldChange.bind(this)}
|
||||
/>
|
||||
: ''
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<SubHeader />
|
||||
<section className="card">
|
||||
<div className="card__content">
|
||||
<h3>{__('Download Directory')}</h3>
|
||||
</div>
|
||||
<div className="card__content">
|
||||
<FormRow
|
||||
type="directory"
|
||||
name="download_directory"
|
||||
defaultValue={daemonSettings.download_directory}
|
||||
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>
|
||||
</div>
|
||||
<div className="card__content">
|
||||
<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);
|
||||
}}
|
||||
defaultChecked={!this.state.isMaxUpload}
|
||||
label={__('Unlimited')}
|
||||
/>
|
||||
<div className="form-row">
|
||||
<FormField
|
||||
type="radio"
|
||||
name="max_upload_pref"
|
||||
onChange={() => {
|
||||
this.onMaxUploadPrefChange(true);
|
||||
}}
|
||||
defaultChecked={this.state.isMaxUpload}
|
||||
label={
|
||||
this.state.isMaxUpload ? __('Up to') : __('Choose limit...')
|
||||
}
|
||||
/>
|
||||
{this.state.isMaxUpload
|
||||
? <FormField
|
||||
type="number"
|
||||
min="0"
|
||||
step=".5"
|
||||
defaultValue={daemonSettings.max_upload}
|
||||
placeholder="10"
|
||||
className="form-field__input--inline"
|
||||
onChange={this.onMaxUploadFieldChange.bind(this)}
|
||||
/>
|
||||
: ''}
|
||||
{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')}
|
||||
type="radio"
|
||||
name="max_download_pref"
|
||||
onChange={() => {
|
||||
this.onMaxDownloadPrefChange(false);
|
||||
}}
|
||||
defaultChecked={!this.state.isMaxDownload}
|
||||
/>
|
||||
<div className="form-row">
|
||||
<FormField
|
||||
type="radio"
|
||||
name="max_download_pref"
|
||||
onChange={() => {
|
||||
this.onMaxDownloadPrefChange(true);
|
||||
}}
|
||||
defaultChecked={this.state.isMaxDownload}
|
||||
label={
|
||||
this.state.isMaxDownload ? __('Up to') : __('Choose limit...')
|
||||
}
|
||||
/>
|
||||
{this.state.isMaxDownload
|
||||
? <FormField
|
||||
type="number"
|
||||
min="0"
|
||||
step=".5"
|
||||
defaultValue={daemonSettings.max_download}
|
||||
placeholder="10"
|
||||
className="form-field__input--inline"
|
||||
onChange={this.onMaxDownloadFieldChange.bind(this)}
|
||||
/>
|
||||
: ''}
|
||||
{this.state.isMaxDownload
|
||||
? <span className="form-field__label">MB/s</span>
|
||||
: ''}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="card">
|
||||
<div className="card__content">
|
||||
<h3>{__('Content')}</h3>
|
||||
</div>
|
||||
<div className="card__content">
|
||||
<FormRow
|
||||
type="checkbox"
|
||||
onChange={this.onShowUnavailableChange.bind(this)}
|
||||
defaultChecked={this.state.showUnavailable}
|
||||
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. '
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
}
|
||||
{ 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")}
|
||||
type="radio"
|
||||
name="max_download_pref"
|
||||
onChange={() => { this.onMaxDownloadPrefChange(false) }}
|
||||
defaultChecked={!this.state.isMaxDownload} />
|
||||
<div className="form-row">
|
||||
<FormField type="radio"
|
||||
name="max_download_pref"
|
||||
onChange={() => { this.onMaxDownloadPrefChange(true) }}
|
||||
defaultChecked={this.state.isMaxDownload}
|
||||
label={ this.state.isMaxDownload ? __("Up to") : __("Choose limit...") } />
|
||||
{ this.state.isMaxDownload ?
|
||||
<FormField type="number"
|
||||
min="0"
|
||||
step=".5"
|
||||
defaultValue={daemonSettings.max_download}
|
||||
placeholder="10"
|
||||
className="form-field__input--inline"
|
||||
onChange={this.onMaxDownloadFieldChange.bind(this)}
|
||||
/>
|
||||
: ''
|
||||
|
||||
}
|
||||
{ this.state.isMaxDownload ? <span className="form-field__label">MB/s</span> : '' }
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="card">
|
||||
<div className="card__content">
|
||||
<h3>{__("Content")}</h3>
|
||||
</div>
|
||||
<div className="card__content">
|
||||
<FormRow type="checkbox"
|
||||
onChange={this.onShowUnavailableChange.bind(this)}
|
||||
defaultChecked={this.state.showUnavailable}
|
||||
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. ")} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/*}
|
||||
{/*}
|
||||
<section className="card">
|
||||
<div className="card__content">
|
||||
<h3>{__("Language")}</h3>
|
||||
|
@ -217,20 +252,24 @@ class SettingsPage extends React.Component {
|
|||
</div>
|
||||
</section>*/}
|
||||
|
||||
<section className="card">
|
||||
<div className="card__content">
|
||||
<h3>{__("Share Diagnostic Data")}</h3>
|
||||
</div>
|
||||
<div className="card__content">
|
||||
<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")} />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
<section className="card">
|
||||
<div className="card__content">
|
||||
<h3>{__('Share Diagnostic Data')}</h3>
|
||||
</div>
|
||||
<div className="card__content">
|
||||
<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'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SettingsPage;
|
||||
|
|
|
@ -1,32 +1,24 @@
|
|||
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(),
|
||||
selectIsResolving = makeSelectIsResolvingForUri();
|
||||
const selectClaim = makeSelectClaimForUri(),
|
||||
selectIsResolving = makeSelectIsResolvingForUri();
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: selectClaim(state, props),
|
||||
isResolvingUri: selectIsResolving(state, props)
|
||||
})
|
||||
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);
|
||||
|
|
|
@ -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{
|
||||
componentWillMount() {
|
||||
this.resolve(this.props)
|
||||
}
|
||||
class ShowPage extends React.Component {
|
||||
componentWillMount() {
|
||||
this.resolve(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.resolve(nextProps)
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.resolve(nextProps);
|
||||
}
|
||||
|
||||
resolve(props) {
|
||||
const {
|
||||
isResolvingUri,
|
||||
resolveUri,
|
||||
claim,
|
||||
uri,
|
||||
} = props
|
||||
resolve(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
|
||||
render() {
|
||||
const { claim, uri, isResolvingUri } = this.props;
|
||||
|
||||
let innerContent = "";
|
||||
let innerContent = '';
|
||||
|
||||
if (isResolvingUri || !claim) {
|
||||
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> }
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
else if (claim.name.length && claim.name[0] === '@') {
|
||||
innerContent = <ChannelPage uri={uri} />
|
||||
}
|
||||
else if (claim) {
|
||||
innerContent = <FilePage uri={uri} />
|
||||
}
|
||||
if (isResolvingUri || !claim) {
|
||||
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>}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
} 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;
|
||||
|
|
|
@ -2,18 +2,18 @@ import React from 'react';
|
|||
import lbry from '../lbry.js';
|
||||
|
||||
class StartPage extends React.Component {
|
||||
componentWillMount() {
|
||||
lbry.stop();
|
||||
}
|
||||
componentWillMount() {
|
||||
lbry.stop();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<h3>{__("LBRY is Closed")}</h3>
|
||||
<Link href="lbry://lbry" label={__("Click here to start LBRY")} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<h3>{__('LBRY is Closed')}</h3>
|
||||
<Link href="lbry://lbry" label={__('Click here to start LBRY')} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default StartPage;
|
||||
|
|
|
@ -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) => ({
|
||||
currentPage: selectCurrentPage(state),
|
||||
balance: selectBalance(state)
|
||||
})
|
||||
const select = state => ({
|
||||
currentPage: selectCurrentPage(state),
|
||||
balance: selectBalance(state)
|
||||
});
|
||||
|
||||
export default connect(select, null)(WalletPage)
|
||||
export default connect(select, null)(WalletPage);
|
||||
|
|
|
@ -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>
|
||||
</div>
|
||||
<div className="card__content">
|
||||
<CreditAmount amount={balance} precision={8} />
|
||||
</div>
|
||||
</section>
|
||||
{ currentPage === 'wallet' ? <TransactionList {...props} /> : '' }
|
||||
{ currentPage === 'send' ? <WalletSend {...props} /> : '' }
|
||||
{ currentPage === 'receive' ? <WalletAddress /> : '' }
|
||||
</main>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<main className="main--single-column">
|
||||
<SubHeader />
|
||||
<section className="card">
|
||||
<div className="card__title-primary">
|
||||
<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 /> : ''}
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default WalletPage;
|
||||
|
|
|
@ -1,132 +1,127 @@
|
|||
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',
|
||||
platform: process.platform,
|
||||
upgradeSkipped: sessionStorage.getItem('upgradeSkipped'),
|
||||
daemonReady: false,
|
||||
obscureNsfw: !lbry.getClientSetting('showNsfw'),
|
||||
hasSignature: false,
|
||||
}
|
||||
isLoaded: false,
|
||||
currentPath: 'discover',
|
||||
platform: process.platform,
|
||||
upgradeSkipped: sessionStorage.getItem('upgradeSkipped'),
|
||||
daemonReady: false,
|
||||
obscureNsfw: !lbry.getClientSetting('showNsfw'),
|
||||
hasSignature: false
|
||||
};
|
||||
|
||||
reducers[types.DAEMON_READY] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
daemonReady: true,
|
||||
})
|
||||
}
|
||||
return Object.assign({}, state, {
|
||||
daemonReady: true
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.CHANGE_PATH] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
currentPath: action.data.path,
|
||||
})
|
||||
}
|
||||
return Object.assign({}, state, {
|
||||
currentPath: action.data.path
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.UPGRADE_CANCELLED] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
downloadProgress: null,
|
||||
upgradeDownloadComplete: false,
|
||||
modal: null,
|
||||
})
|
||||
}
|
||||
return Object.assign({}, state, {
|
||||
downloadProgress: null,
|
||||
upgradeDownloadComplete: false,
|
||||
modal: null
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.UPGRADE_DOWNLOAD_COMPLETED] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
downloadPath: action.data.path,
|
||||
upgradeDownloading: false,
|
||||
upgradeDownloadCompleted: true
|
||||
})
|
||||
}
|
||||
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
|
||||
})
|
||||
}
|
||||
return Object.assign({}, state, {
|
||||
upgradeDownloading: true
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.SKIP_UPGRADE] = function(state, action) {
|
||||
sessionStorage.setItem('upgradeSkipped', true);
|
||||
sessionStorage.setItem('upgradeSkipped', true);
|
||||
|
||||
return Object.assign({}, state, {
|
||||
upgradeSkipped: true,
|
||||
modal: null
|
||||
})
|
||||
}
|
||||
return Object.assign({}, state, {
|
||||
upgradeSkipped: true,
|
||||
modal: null
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.UPDATE_VERSION] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
version: action.data.version
|
||||
})
|
||||
}
|
||||
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
|
||||
})
|
||||
}
|
||||
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
|
||||
})
|
||||
}
|
||||
return Object.assign({}, state, {
|
||||
modal: undefined,
|
||||
modalExtraContent: undefined
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.UPGRADE_DOWNLOAD_PROGRESSED] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
downloadProgress: action.data.percent
|
||||
})
|
||||
}
|
||||
return Object.assign({}, state, {
|
||||
downloadProgress: action.data.percent
|
||||
});
|
||||
};
|
||||
|
||||
reducers[types.DAEMON_READY] = function(state, action) {
|
||||
return Object.assign({}, state, {
|
||||
daemonReady: true
|
||||
})
|
||||
}
|
||||
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)
|
||||
snacks.push({
|
||||
message,
|
||||
linkText,
|
||||
linkTarget,
|
||||
isError,
|
||||
})
|
||||
const newSnackBar = 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
|
||||
});
|
||||
const newSnackBar = Object.assign({}, snackBar, {
|
||||
snacks
|
||||
});
|
||||
|
||||
return Object.assign({}, state, {
|
||||
snackBar: newSnackBar,
|
||||
})
|
||||
}
|
||||
return Object.assign({}, state, {
|
||||
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,
|
||||
})
|
||||
const newSnackBar = Object.assign({}, snackBar, {
|
||||
snacks
|
||||
});
|
||||
|
||||
return Object.assign({}, state, {
|
||||
snackBar: newSnackBar,
|
||||
})
|
||||
}
|
||||
return Object.assign({}, state, {
|
||||
snackBar: newSnackBar
|
||||
});
|
||||
};
|
||||
|
||||
export default function reducer(state = defaultState, action) {
|
||||
const handler = reducers[action.type];
|
||||
if (handler) return handler(state, action);
|
||||
return state;
|
||||
const handler = reducers[action.type];
|
||||
if (handler) return handler(state, action);
|
||||
return state;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue