Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
e83ace19c5
162 changed files with 15033 additions and 5196 deletions
|
@ -1,5 +1,5 @@
|
||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 0.11.9
|
current_version = 0.12.0
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)((?P<release>[a-z]+)(?P<candidate>\d+))?
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)((?P<release>[a-z]+)(?P<candidate>\d+))?
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -16,3 +16,4 @@ dist
|
||||||
.#*
|
.#*
|
||||||
|
|
||||||
build/daemon.zip
|
build/daemon.zip
|
||||||
|
.vimrc
|
||||||
|
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -27,6 +27,28 @@ Web UI version numbers should always match the corresponding version of LBRY App
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
|
|
||||||
|
## [0.12.0] - 2017-06-09
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* More file types, like audio and documents, can be streamed and/or served from the app
|
||||||
|
* App is no longer gated. Reward authorization re-written. Added basic flows for new users.
|
||||||
|
* Videos now have a classy loading spinner
|
||||||
|
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* All UI strings are now rendered according to gettext standard, in prep for i18n
|
||||||
|
* Switched to new fee metadata format
|
||||||
|
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* If a daemon is running but unresponsive, startup is no longer blocked indefinitely
|
||||||
|
* Updated deprecated LBRY API call signatures
|
||||||
|
* App scrolls to the top of the page on navigation
|
||||||
|
* Download progress works properly for purchased but deleted files
|
||||||
|
* Publish channels for less than 1 LBC
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [0.11.9] - 2017-06-01
|
## [0.11.9] - 2017-06-01
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -48,3 +48,7 @@ to create distributable packages, which is run by calling:
|
||||||
|
|
||||||
This project has currently only been worked on in Linux and macOS. If you are on Windows, you can
|
This project has currently only been worked on in Linux and macOS. If you are on Windows, you can
|
||||||
checkout out the build steps in [appveyor.yml](https://github.com/lbryio/lbry-app/blob/master/.appveyor.yml) and probably figure out something from there.
|
checkout out the build steps in [appveyor.yml](https://github.com/lbryio/lbry-app/blob/master/.appveyor.yml) and probably figure out something from there.
|
||||||
|
|
||||||
|
## Internationalization
|
||||||
|
|
||||||
|
If you want to help translating the lbry-app, you can copy the en.json file in /app/locales and modify the values while leaving the keys as their original English strings. An example for this would be: `"Skip": "Überspringen",` Translations should automatically show up in options.
|
|
@ -21,7 +21,13 @@ const VERSION_CHECK_INTERVAL = 30 * 60 * 1000;
|
||||||
const LATEST_RELEASE_API_URL = 'https://api.github.com/repos/lbryio/lbry-app/releases/latest';
|
const LATEST_RELEASE_API_URL = 'https://api.github.com/repos/lbryio/lbry-app/releases/latest';
|
||||||
|
|
||||||
|
|
||||||
let client = jayson.client.http('http://localhost:5279/lbryapi');
|
let client = jayson.client.http({
|
||||||
|
host: 'localhost',
|
||||||
|
port: 5279,
|
||||||
|
path: '/lbryapi',
|
||||||
|
timeout: 1000
|
||||||
|
});
|
||||||
|
|
||||||
// Keep a global reference of the window object, if you don't, the window will
|
// Keep a global reference of the window object, if you don't, the window will
|
||||||
// be closed automatically when the JavaScript object is garbage collected.
|
// be closed automatically when the JavaScript object is garbage collected.
|
||||||
let win;
|
let win;
|
||||||
|
|
1470
app/package-lock.json
generated
Normal file
1470
app/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "LBRY",
|
"name": "LBRY",
|
||||||
"version": "0.11.9",
|
"version": "0.12.0",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"description": "LBRY is a fully decentralized, open-source protocol facilitating the discovery, access, and (sometimes) purchase of data.",
|
"description": "LBRY is a fully decentralized, open-source protocol facilitating the discovery, access, and (sometimes) purchase of data.",
|
||||||
"author": {
|
"author": {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
https://github.com/lbryio/lbry/releases/download/v0.10.3/lbrynet-daemon-v0.10.3-OSNAME.zip
|
https://github.com/lbryio/lbry/releases/download/v0.11.0/lbrynet-daemon-v0.11.0-OSNAME.zip
|
||||||
|
|
1764
package-lock.json
generated
Normal file
1764
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
14
package.json
14
package.json
|
@ -32,11 +32,15 @@
|
||||||
},
|
},
|
||||||
"backgroundColor": "155B4A"
|
"backgroundColor": "155B4A"
|
||||||
},
|
},
|
||||||
"protocols": [{
|
"protocols": [
|
||||||
"name": "lbry",
|
{
|
||||||
"role": "Viewer",
|
"name": "lbry",
|
||||||
"schemes": ["lbry"]
|
"role": "Viewer",
|
||||||
}],
|
"schemes": [
|
||||||
|
"lbry"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"linux": {
|
"linux": {
|
||||||
"target": "deb",
|
"target": "deb",
|
||||||
"desktop": {
|
"desktop": {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as types from 'constants/action_types'
|
import * as types from "constants/action_types";
|
||||||
import lbry from 'lbry'
|
import lbry from "lbry";
|
||||||
import {
|
import {
|
||||||
selectUpdateUrl,
|
selectUpdateUrl,
|
||||||
selectUpgradeDownloadPath,
|
selectUpgradeDownloadPath,
|
||||||
|
@ -8,36 +8,34 @@ import {
|
||||||
selectPageTitle,
|
selectPageTitle,
|
||||||
selectCurrentPage,
|
selectCurrentPage,
|
||||||
selectCurrentParams,
|
selectCurrentParams,
|
||||||
} from 'selectors/app'
|
} from "selectors/app";
|
||||||
import {
|
import { doSearch } from "actions/search";
|
||||||
doSearch,
|
import { doFetchDaemonSettings } from "actions/settings";
|
||||||
} from 'actions/search'
|
import { doAuthenticate } from "actions/user";
|
||||||
|
import { doRewardList } from "actions/rewards";
|
||||||
|
import { doFileList } from "actions/file_info";
|
||||||
|
|
||||||
const {remote, ipcRenderer, shell} = require('electron');
|
const { remote, ipcRenderer, shell } = require("electron");
|
||||||
const path = require('path');
|
const path = require("path");
|
||||||
const app = require('electron').remote.app;
|
const app = require("electron").remote.app;
|
||||||
const {download} = remote.require('electron-dl');
|
const { download } = remote.require("electron-dl");
|
||||||
const fs = remote.require('fs');
|
const fs = remote.require("fs");
|
||||||
|
|
||||||
const queryStringFromParams = (params) => {
|
const queryStringFromParams = params => {
|
||||||
return Object
|
return Object.keys(params).map(key => `${key}=${params[key]}`).join("&");
|
||||||
.keys(params)
|
};
|
||||||
.map(key => `${key}=${params[key]}`)
|
|
||||||
.join('&')
|
|
||||||
}
|
|
||||||
|
|
||||||
export function doNavigate(path, params = {}) {
|
export function doNavigate(path, params = {}) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
let url = path
|
let url = path;
|
||||||
if (params)
|
if (params) url = `${url}?${queryStringFromParams(params)}`;
|
||||||
url = `${url}?${queryStringFromParams(params)}`
|
|
||||||
|
|
||||||
dispatch(doChangePath(url))
|
dispatch(doChangePath(url));
|
||||||
|
|
||||||
const state = getState()
|
const state = getState();
|
||||||
const pageTitle = selectPageTitle(state)
|
const pageTitle = selectPageTitle(state);
|
||||||
dispatch(doHistoryPush(params, pageTitle, url))
|
dispatch(doHistoryPush(params, pageTitle, url));
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doChangePath(path) {
|
export function doChangePath(path) {
|
||||||
|
@ -46,121 +44,124 @@ export function doChangePath(path) {
|
||||||
type: types.CHANGE_PATH,
|
type: types.CHANGE_PATH,
|
||||||
data: {
|
data: {
|
||||||
path,
|
path,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
const state = getState()
|
const state = getState();
|
||||||
const pageTitle = selectPageTitle(state)
|
const pageTitle = selectPageTitle(state);
|
||||||
window.document.title = pageTitle
|
window.document.title = pageTitle;
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0);
|
||||||
|
|
||||||
const currentPage = selectCurrentPage(state)
|
const currentPage = selectCurrentPage(state);
|
||||||
if (currentPage === 'search') {
|
if (currentPage === "search") {
|
||||||
const params = selectCurrentParams(state)
|
const params = selectCurrentParams(state);
|
||||||
dispatch(doSearch(params.query))
|
dispatch(doSearch(params.query));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doHistoryBack() {
|
export function doHistoryBack() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
history.back()
|
history.back();
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doHistoryPush(params, title, relativeUrl) {
|
export function doHistoryPush(params, title, relativeUrl) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
let pathParts = window.location.pathname.split('/')
|
let pathParts = window.location.pathname.split("/");
|
||||||
pathParts[pathParts.length - 1] = relativeUrl.replace(/^\//, '')
|
pathParts[pathParts.length - 1] = relativeUrl.replace(/^\//, "");
|
||||||
const url = pathParts.join('/')
|
const url = pathParts.join("/");
|
||||||
title += " - LBRY"
|
title += " - LBRY";
|
||||||
history.pushState(params, title, url)
|
history.pushState(params, title, url);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doOpenModal(modal) {
|
export function doOpenModal(modal) {
|
||||||
return {
|
return {
|
||||||
type: types.OPEN_MODAL,
|
type: types.OPEN_MODAL,
|
||||||
data: {
|
data: {
|
||||||
modal
|
modal,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doCloseModal() {
|
export function doCloseModal() {
|
||||||
return {
|
return {
|
||||||
type: types.CLOSE_MODAL,
|
type: types.CLOSE_MODAL,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doUpdateDownloadProgress(percent) {
|
export function doUpdateDownloadProgress(percent) {
|
||||||
return {
|
return {
|
||||||
type: types.UPGRADE_DOWNLOAD_PROGRESSED,
|
type: types.UPGRADE_DOWNLOAD_PROGRESSED,
|
||||||
data: {
|
data: {
|
||||||
percent: percent
|
percent: percent,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doSkipUpgrade() {
|
export function doSkipUpgrade() {
|
||||||
return {
|
return {
|
||||||
type: types.SKIP_UPGRADE
|
type: types.SKIP_UPGRADE,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doStartUpgrade() {
|
export function doStartUpgrade() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
const upgradeDownloadPath = selectUpgradeDownloadPath(state)
|
const upgradeDownloadPath = selectUpgradeDownloadPath(state);
|
||||||
|
|
||||||
ipcRenderer.send('upgrade', upgradeDownloadPath)
|
ipcRenderer.send("upgrade", upgradeDownloadPath);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doDownloadUpgrade() {
|
export function doDownloadUpgrade() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
// Make a new directory within temp directory so the filename is guaranteed to be available
|
// 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 dir = fs.mkdtempSync(app.getPath("temp") + require("path").sep);
|
||||||
const upgradeFilename = selectUpgradeFilename(state)
|
const upgradeFilename = selectUpgradeFilename(state);
|
||||||
|
|
||||||
let options = {
|
let options = {
|
||||||
onProgress: (p) => dispatch(doUpdateDownloadProgress(Math.round(p * 100))),
|
onProgress: p => dispatch(doUpdateDownloadProgress(Math.round(p * 100))),
|
||||||
directory: dir,
|
directory: dir,
|
||||||
};
|
};
|
||||||
download(remote.getCurrentWindow(), selectUpdateUrl(state), options)
|
download(
|
||||||
.then(downloadItem => {
|
remote.getCurrentWindow(),
|
||||||
/**
|
selectUpdateUrl(state),
|
||||||
|
options
|
||||||
|
).then(downloadItem => {
|
||||||
|
/**
|
||||||
* TODO: get the download path directly from the download object. It should just be
|
* 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
|
* downloadItem.getSavePath(), but the copy on the main process is being garbage collected
|
||||||
* too soon.
|
* too soon.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.UPGRADE_DOWNLOAD_COMPLETED,
|
type: types.UPGRADE_DOWNLOAD_COMPLETED,
|
||||||
data: {
|
data: {
|
||||||
downloadItem,
|
downloadItem,
|
||||||
path: path.join(dir, upgradeFilename)
|
path: path.join(dir, upgradeFilename),
|
||||||
}
|
},
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.UPGRADE_DOWNLOAD_STARTED
|
type: types.UPGRADE_DOWNLOAD_STARTED,
|
||||||
})
|
});
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.OPEN_MODAL,
|
type: types.OPEN_MODAL,
|
||||||
data: {
|
data: {
|
||||||
modal: 'downloading'
|
modal: "downloading",
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doCancelUpgrade() {
|
export function doCancelUpgrade() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
const upgradeDownloadItem = selectUpgradeDownloadItem(state)
|
const upgradeDownloadItem = selectUpgradeDownloadItem(state);
|
||||||
|
|
||||||
if (upgradeDownloadItem) {
|
if (upgradeDownloadItem) {
|
||||||
/*
|
/*
|
||||||
|
@ -171,68 +172,74 @@ export function doCancelUpgrade() {
|
||||||
try {
|
try {
|
||||||
upgradeDownloadItem.cancel();
|
upgradeDownloadItem.cancel();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err);
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({ type: types.UPGRADE_CANCELLED })
|
dispatch({ type: types.UPGRADE_CANCELLED });
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doCheckUpgradeAvailable() {
|
export function doCheckUpgradeAvailable() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
|
|
||||||
lbry.getAppVersionInfo().then(({remoteVersion, upgradeAvailable}) => {
|
lbry.getAppVersionInfo().then(({ remoteVersion, upgradeAvailable }) => {
|
||||||
if (upgradeAvailable) {
|
if (upgradeAvailable) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.UPDATE_VERSION,
|
type: types.UPDATE_VERSION,
|
||||||
data: {
|
data: {
|
||||||
version: remoteVersion,
|
version: remoteVersion,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.OPEN_MODAL,
|
type: types.OPEN_MODAL,
|
||||||
data: {
|
data: {
|
||||||
modal: 'upgrade'
|
modal: "upgrade",
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doAlertError(errorList) {
|
export function doAlertError(errorList) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
console.log('do alert error')
|
console.log("do alert error");
|
||||||
console.log(errorList)
|
console.log(errorList);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.OPEN_MODAL,
|
type: types.OPEN_MODAL,
|
||||||
data: {
|
data: {
|
||||||
modal: 'error',
|
modal: "error",
|
||||||
extraContent: errorList
|
extraContent: errorList,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doDaemonReady() {
|
export function doDaemonReady() {
|
||||||
return {
|
return function(dispatch, getState) {
|
||||||
type: types.DAEMON_READY
|
dispatch(doAuthenticate());
|
||||||
}
|
dispatch({
|
||||||
|
type: types.DAEMON_READY,
|
||||||
|
});
|
||||||
|
dispatch(doChangePath("/discover"));
|
||||||
|
dispatch(doFetchDaemonSettings());
|
||||||
|
dispatch(doFileList());
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doShowSnackBar(data) {
|
export function doShowSnackBar(data) {
|
||||||
return {
|
return {
|
||||||
type: types.SHOW_SNACKBAR,
|
type: types.SHOW_SNACKBAR,
|
||||||
data,
|
data,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doRemoveSnackBarSnack() {
|
export function doRemoveSnackBarSnack() {
|
||||||
return {
|
return {
|
||||||
type: types.REMOVE_SNACKBAR_SNACK,
|
type: types.REMOVE_SNACKBAR_SNACK,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,27 @@
|
||||||
import * as types from 'constants/action_types'
|
import * as types from "constants/action_types";
|
||||||
import lbry from 'lbry'
|
import lbry from "lbry";
|
||||||
import {
|
import { selectFetchingAvailability } from "selectors/availability";
|
||||||
selectFetchingAvailability
|
|
||||||
} from 'selectors/availability'
|
|
||||||
|
|
||||||
export function doFetchAvailability(uri) {
|
export function doFetchAvailability(uri) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
const alreadyFetching = !!selectFetchingAvailability(state)[uri]
|
const alreadyFetching = !!selectFetchingAvailability(state)[uri];
|
||||||
|
|
||||||
if (!alreadyFetching) {
|
if (!alreadyFetching) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FETCH_AVAILABILITY_STARTED,
|
type: types.FETCH_AVAILABILITY_STARTED,
|
||||||
data: {uri}
|
data: { uri },
|
||||||
})
|
});
|
||||||
|
|
||||||
lbry.get_availability({uri}).then((availability) => {
|
lbry.get_availability({ uri }).then(availability => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FETCH_AVAILABILITY_COMPLETED,
|
type: types.FETCH_AVAILABILITY_COMPLETED,
|
||||||
data: {
|
data: {
|
||||||
availability,
|
availability,
|
||||||
uri,
|
uri,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
|
@ -1,47 +1,34 @@
|
||||||
import * as types from 'constants/action_types'
|
import * as types from "constants/action_types";
|
||||||
import lbry from 'lbry'
|
import lbry from "lbry";
|
||||||
import lbryio from 'lbryio'
|
import lbryio from "lbryio";
|
||||||
import lbryuri from 'lbryuri'
|
import lbryuri from "lbryuri";
|
||||||
import rewards from 'rewards'
|
import { selectBalance } from "selectors/wallet";
|
||||||
import {
|
|
||||||
selectBalance,
|
|
||||||
} from 'selectors/wallet'
|
|
||||||
import {
|
import {
|
||||||
selectFileInfoForUri,
|
selectFileInfoForUri,
|
||||||
selectUrisDownloading,
|
selectUrisDownloading,
|
||||||
} from 'selectors/file_info'
|
} from "selectors/file_info";
|
||||||
import {
|
import { selectResolvingUris } from "selectors/content";
|
||||||
selectResolvingUris
|
import { selectCostInfoForUri } from "selectors/cost_info";
|
||||||
} from 'selectors/content'
|
import { doOpenModal } from "actions/app";
|
||||||
import {
|
import { doClaimEligiblePurchaseRewards } from "actions/rewards";
|
||||||
selectCostInfoForUri,
|
|
||||||
} from 'selectors/cost_info'
|
|
||||||
import {
|
|
||||||
selectClaimsByUri,
|
|
||||||
} from 'selectors/claims'
|
|
||||||
import {
|
|
||||||
doOpenModal,
|
|
||||||
} from 'actions/app'
|
|
||||||
|
|
||||||
export function doResolveUri(uri) {
|
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) {
|
if (!alreadyResolving) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.RESOLVE_URI_STARTED,
|
type: types.RESOLVE_URI_STARTED,
|
||||||
data: { uri }
|
data: { uri },
|
||||||
})
|
});
|
||||||
|
|
||||||
lbry.resolve({ uri }).then((resolutionInfo) => {
|
lbry.resolve({ uri }).then(resolutionInfo => {
|
||||||
const {
|
const { claim, certificate } = resolutionInfo
|
||||||
claim,
|
? resolutionInfo
|
||||||
certificate,
|
: { claim: null, certificate: null };
|
||||||
} = resolutionInfo ? resolutionInfo : { claim : null, certificate: null }
|
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.RESOLVE_URI_COMPLETED,
|
type: types.RESOLVE_URI_COMPLETED,
|
||||||
|
@ -49,246 +36,252 @@ export function doResolveUri(uri) {
|
||||||
uri,
|
uri,
|
||||||
claim,
|
claim,
|
||||||
certificate,
|
certificate,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doCancelResolveUri(uri) {
|
export function doCancelResolveUri(uri) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
lbry.cancelResolve({ uri })
|
lbry.cancelResolve({ uri });
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.RESOLVE_URI_CANCELED,
|
type: types.RESOLVE_URI_CANCELED,
|
||||||
data: { uri }
|
data: { uri },
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doFetchFeaturedUris() {
|
export function doFetchFeaturedUris() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FETCH_FEATURED_CONTENT_STARTED,
|
type: types.FETCH_FEATURED_CONTENT_STARTED,
|
||||||
})
|
});
|
||||||
|
|
||||||
const success = ({ Categories, Uris }) => {
|
const success = ({ Categories, Uris }) => {
|
||||||
|
let featuredUris = {};
|
||||||
|
|
||||||
let featuredUris = {}
|
Categories.forEach(category => {
|
||||||
|
|
||||||
Categories.forEach((category) => {
|
|
||||||
if (Uris[category] && Uris[category].length) {
|
if (Uris[category] && Uris[category].length) {
|
||||||
featuredUris[category] = Uris[category]
|
featuredUris[category] = Uris[category];
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FETCH_FEATURED_CONTENT_COMPLETED,
|
type: types.FETCH_FEATURED_CONTENT_COMPLETED,
|
||||||
data: {
|
data: {
|
||||||
categories: Categories,
|
categories: Categories,
|
||||||
uris: featuredUris,
|
uris: featuredUris,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const failure = () => {
|
const failure = () => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FETCH_FEATURED_CONTENT_COMPLETED,
|
type: types.FETCH_FEATURED_CONTENT_COMPLETED,
|
||||||
data: {
|
data: {
|
||||||
categories: [],
|
categories: [],
|
||||||
uris: {}
|
uris: {},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
lbryio.call('discover', 'list', { version: "early-access" } )
|
lbryio
|
||||||
.then(success, failure)
|
.call("discover", "list", { version: "early-access" })
|
||||||
}
|
.then(success, failure);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doUpdateLoadStatus(uri, outpoint) {
|
export function doUpdateLoadStatus(uri, outpoint) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
|
|
||||||
lbry.file_list({
|
lbry
|
||||||
outpoint: outpoint,
|
.file_list({
|
||||||
full_status: true,
|
outpoint: outpoint,
|
||||||
}).then(([fileInfo]) => {
|
full_status: true,
|
||||||
if(!fileInfo || fileInfo.written_bytes == 0) {
|
})
|
||||||
// download hasn't started yet
|
.then(([fileInfo]) => {
|
||||||
setTimeout(() => { dispatch(doUpdateLoadStatus(uri, outpoint)) }, 250)
|
if (!fileInfo || fileInfo.written_bytes == 0) {
|
||||||
} else if (fileInfo.completed) {
|
// download hasn't started yet
|
||||||
// TODO this isn't going to get called if they reload the client before
|
setTimeout(() => {
|
||||||
// the download finished
|
dispatch(doUpdateLoadStatus(uri, outpoint));
|
||||||
dispatch({
|
}, 250);
|
||||||
type: types.DOWNLOADING_COMPLETED,
|
} else if (fileInfo.completed) {
|
||||||
data: {
|
// TODO this isn't going to get called if they reload the client before
|
||||||
uri,
|
// the download finished
|
||||||
outpoint,
|
dispatch({
|
||||||
fileInfo,
|
type: types.DOWNLOADING_COMPLETED,
|
||||||
}
|
data: {
|
||||||
})
|
uri,
|
||||||
} else {
|
outpoint,
|
||||||
// ready to play
|
fileInfo,
|
||||||
const {
|
},
|
||||||
total_bytes,
|
});
|
||||||
written_bytes,
|
} else {
|
||||||
} = fileInfo
|
// ready to play
|
||||||
const progress = (written_bytes / total_bytes) * 100
|
const { total_bytes, written_bytes } = fileInfo;
|
||||||
|
const progress = written_bytes / total_bytes * 100;
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.DOWNLOADING_PROGRESSED,
|
type: types.DOWNLOADING_PROGRESSED,
|
||||||
data: {
|
data: {
|
||||||
uri,
|
uri,
|
||||||
outpoint,
|
outpoint,
|
||||||
fileInfo,
|
fileInfo,
|
||||||
progress,
|
progress,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
setTimeout(() => { dispatch(doUpdateLoadStatus(uri, outpoint)) }, 250)
|
setTimeout(() => {
|
||||||
}
|
dispatch(doUpdateLoadStatus(uri, outpoint));
|
||||||
})
|
}, 250);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doDownloadFile(uri, streamInfo) {
|
export function doDownloadFile(uri, streamInfo) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
|
|
||||||
lbry.file_list({ outpoint: streamInfo.outpoint, full_status: true }).then(([fileInfo]) => {
|
lbry
|
||||||
dispatch({
|
.file_list({ outpoint: streamInfo.outpoint, full_status: true })
|
||||||
type: types.DOWNLOADING_STARTED,
|
.then(([fileInfo]) => {
|
||||||
data: {
|
dispatch({
|
||||||
uri,
|
type: types.DOWNLOADING_STARTED,
|
||||||
outpoint: streamInfo.outpoint,
|
data: {
|
||||||
fileInfo,
|
uri,
|
||||||
}
|
outpoint: streamInfo.outpoint,
|
||||||
|
fileInfo,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(doUpdateLoadStatus(uri, streamInfo.outpoint));
|
||||||
|
});
|
||||||
|
|
||||||
|
lbryio
|
||||||
|
.call("file", "view", {
|
||||||
|
uri: uri,
|
||||||
|
outpoint: streamInfo.outpoint,
|
||||||
|
claim_id: streamInfo.claim_id,
|
||||||
})
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
dispatch(doUpdateLoadStatus(uri, streamInfo.outpoint))
|
dispatch(doClaimEligiblePurchaseRewards());
|
||||||
})
|
};
|
||||||
|
|
||||||
lbryio.call('file', 'view', {
|
|
||||||
uri: uri,
|
|
||||||
outpoint: streamInfo.outpoint,
|
|
||||||
claim_id: streamInfo.claim_id,
|
|
||||||
}).catch(() => {})
|
|
||||||
|
|
||||||
rewards.claimEligiblePurchaseRewards()
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doLoadVideo(uri) {
|
export function doLoadVideo(uri) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.LOADING_VIDEO_STARTED,
|
type: types.LOADING_VIDEO_STARTED,
|
||||||
data: {
|
data: {
|
||||||
uri
|
uri,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
lbry.get({ uri }).then(streamInfo => {
|
lbry.get({ uri }).then(streamInfo => {
|
||||||
const timeout = streamInfo === null ||
|
const timeout =
|
||||||
typeof streamInfo !== 'object' ||
|
streamInfo === null ||
|
||||||
streamInfo.error == 'Timeout'
|
typeof streamInfo !== "object" ||
|
||||||
|
streamInfo.error == "Timeout";
|
||||||
|
|
||||||
if(timeout) {
|
if (timeout) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.LOADING_VIDEO_FAILED,
|
type: types.LOADING_VIDEO_FAILED,
|
||||||
data: { uri }
|
data: { uri },
|
||||||
})
|
});
|
||||||
dispatch(doOpenModal('timedOut'))
|
dispatch(doOpenModal("timedOut"));
|
||||||
} else {
|
} else {
|
||||||
dispatch(doDownloadFile(uri, streamInfo))
|
dispatch(doDownloadFile(uri, streamInfo));
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doPurchaseUri(uri, purchaseModalName) {
|
export function doPurchaseUri(uri, purchaseModalName) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
const balance = selectBalance(state)
|
const balance = selectBalance(state);
|
||||||
const fileInfo = selectFileInfoForUri(state, { uri })
|
const fileInfo = selectFileInfoForUri(state, { uri });
|
||||||
const downloadingByUri = selectUrisDownloading(state)
|
const downloadingByUri = selectUrisDownloading(state);
|
||||||
const alreadyDownloading = !!downloadingByUri[uri]
|
const alreadyDownloading = !!downloadingByUri[uri];
|
||||||
|
|
||||||
// we already fully downloaded the file.
|
// we already fully downloaded the file.
|
||||||
if (fileInfo && fileInfo.completed) {
|
if (fileInfo && fileInfo.completed) {
|
||||||
// If written_bytes is false that means the user has deleted/moved the
|
// 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
|
// file manually on their file system, so we need to dispatch a
|
||||||
// doLoadVideo action to reconstruct the file from the blobs
|
// doLoadVideo action to reconstruct the file from the blobs
|
||||||
if (!fileInfo.written_bytes) dispatch(doLoadVideo(uri))
|
if (!fileInfo.written_bytes) dispatch(doLoadVideo(uri));
|
||||||
|
|
||||||
return Promise.resolve()
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
// we are already downloading the file
|
// we are already downloading the file
|
||||||
if (alreadyDownloading) {
|
if (alreadyDownloading) {
|
||||||
return Promise.resolve()
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
const costInfo = selectCostInfoForUri(state, { uri })
|
const costInfo = selectCostInfoForUri(state, { uri });
|
||||||
const { cost } = costInfo
|
const { cost } = costInfo;
|
||||||
|
|
||||||
// the file is free or we have partially downloaded it
|
// the file is free or we have partially downloaded it
|
||||||
if (cost <= 0.01 || (fileInfo && fileInfo.download_directory)) {
|
if (cost <= 0.01 || (fileInfo && fileInfo.download_directory)) {
|
||||||
dispatch(doLoadVideo(uri))
|
dispatch(doLoadVideo(uri));
|
||||||
return Promise.resolve()
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cost > balance) {
|
if (cost > balance) {
|
||||||
dispatch(doOpenModal('notEnoughCredits'))
|
dispatch(doOpenModal("notEnoughCredits"));
|
||||||
} else {
|
} else {
|
||||||
dispatch(doOpenModal(purchaseModalName))
|
dispatch(doOpenModal(purchaseModalName));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve()
|
return Promise.resolve();
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doFetchClaimsByChannel(uri) {
|
export function doFetchClaimsByChannel(uri) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FETCH_CHANNEL_CLAIMS_STARTED,
|
type: types.FETCH_CHANNEL_CLAIMS_STARTED,
|
||||||
data: { uri }
|
data: { uri },
|
||||||
})
|
});
|
||||||
|
|
||||||
lbry.resolve({ uri }).then((resolutionInfo) => {
|
lbry.resolve({ uri }).then(resolutionInfo => {
|
||||||
const {
|
const { claims_in_channel } = resolutionInfo
|
||||||
claims_in_channel,
|
? resolutionInfo
|
||||||
} = resolutionInfo ? resolutionInfo : { claims_in_channel: [] }
|
: { claims_in_channel: [] };
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FETCH_CHANNEL_CLAIMS_COMPLETED,
|
type: types.FETCH_CHANNEL_CLAIMS_COMPLETED,
|
||||||
data: {
|
data: {
|
||||||
uri,
|
uri,
|
||||||
claims: claims_in_channel
|
claims: claims_in_channel,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doFetchClaimListMine() {
|
export function doFetchClaimListMine() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FETCH_CLAIM_LIST_MINE_STARTED
|
type: types.FETCH_CLAIM_LIST_MINE_STARTED,
|
||||||
})
|
});
|
||||||
|
|
||||||
|
lbry.claim_list_mine().then(claims => {
|
||||||
lbry.claim_list_mine().then((claims) => {
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FETCH_CLAIM_LIST_MINE_COMPLETED,
|
type: types.FETCH_CLAIM_LIST_MINE_COMPLETED,
|
||||||
data: {
|
data: {
|
||||||
claims
|
claims,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
|
@ -1,48 +1,26 @@
|
||||||
import * as types from 'constants/action_types'
|
import * as types from "constants/action_types";
|
||||||
import lbry from 'lbry'
|
import lbry from "lbry";
|
||||||
import lbryio from 'lbryio'
|
import lbryio from "lbryio";
|
||||||
import {
|
import { doResolveUri } from "actions/content";
|
||||||
doResolveUri
|
import { selectResolvingUris } from "selectors/content";
|
||||||
} from 'actions/content'
|
import { selectClaimsByUri } from "selectors/claims";
|
||||||
import {
|
import { selectSettingsIsGenerous } from "selectors/settings";
|
||||||
selectResolvingUris,
|
|
||||||
} from 'selectors/content'
|
|
||||||
import {
|
|
||||||
selectClaimsByUri
|
|
||||||
} from 'selectors/claims'
|
|
||||||
import {
|
|
||||||
selectSettingsIsGenerous
|
|
||||||
} from 'selectors/settings'
|
|
||||||
|
|
||||||
export function doFetchCostInfoForUri(uri) {
|
export function doFetchCostInfoForUri(uri) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState(),
|
const state = getState(),
|
||||||
claim = selectClaimsByUri(state)[uri],
|
claim = selectClaimsByUri(state)[uri],
|
||||||
isResolving = selectResolvingUris(state).indexOf(uri) !== -1,
|
isGenerous = selectSettingsIsGenerous(state);
|
||||||
isGenerous = selectSettingsIsGenerous(state)
|
|
||||||
|
|
||||||
if (claim === null) { //claim doesn't exist, nothing to fetch a cost for
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!claim) {
|
|
||||||
setTimeout(() => {
|
|
||||||
dispatch(doFetchCostInfoForUri(uri))
|
|
||||||
}, 1000)
|
|
||||||
if (!isResolving) {
|
|
||||||
dispatch(doResolveUri(uri))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!claim) return null;
|
||||||
|
|
||||||
function begin() {
|
function begin() {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FETCH_COST_INFO_STARTED,
|
type: types.FETCH_COST_INFO_STARTED,
|
||||||
data: {
|
data: {
|
||||||
uri,
|
uri,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolve(costInfo) {
|
function resolve(costInfo) {
|
||||||
|
@ -51,27 +29,26 @@ export function doFetchCostInfoForUri(uri) {
|
||||||
data: {
|
data: {
|
||||||
uri,
|
uri,
|
||||||
costInfo,
|
costInfo,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isGenerous && claim) {
|
if (isGenerous && claim) {
|
||||||
let cost
|
let cost;
|
||||||
const fee = claim.value.stream.metadata.fee;
|
const fee = claim.value.stream.metadata.fee;
|
||||||
if (fee === undefined ) {
|
if (fee === undefined) {
|
||||||
resolve({ cost: 0, includesData: true })
|
resolve({ cost: 0, includesData: true });
|
||||||
} else if (fee.currency == 'LBC') {
|
} else if (fee.currency == "LBC") {
|
||||||
resolve({ cost: fee.amount, includesData: true })
|
resolve({ cost: fee.amount, includesData: true });
|
||||||
} else {
|
} else {
|
||||||
begin()
|
begin();
|
||||||
lbryio.getExchangeRates().then(({lbc_usd}) => {
|
lbryio.getExchangeRates().then(({ lbc_usd }) => {
|
||||||
resolve({ cost: fee.amount / lbc_usd, includesData: true })
|
resolve({ cost: fee.amount / lbc_usd, includesData: true });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
begin()
|
begin();
|
||||||
lbry.getCostInfo(uri).then(resolve)
|
lbry.getCostInfo(uri).then(resolve);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,121 +1,113 @@
|
||||||
import * as types from 'constants/action_types'
|
import * as types from "constants/action_types";
|
||||||
import lbry from 'lbry'
|
import lbry from "lbry";
|
||||||
import {
|
import { doFetchClaimListMine } from "actions/content";
|
||||||
doFetchClaimListMine
|
|
||||||
} from 'actions/content'
|
|
||||||
import {
|
import {
|
||||||
selectClaimsByUri,
|
selectClaimsByUri,
|
||||||
selectClaimListMineIsPending,
|
selectClaimListMineIsPending,
|
||||||
} from 'selectors/claims'
|
} from "selectors/claims";
|
||||||
import {
|
import {
|
||||||
selectFileListIsPending,
|
selectFileListIsPending,
|
||||||
selectAllFileInfos,
|
selectAllFileInfos,
|
||||||
selectUrisLoading,
|
selectUrisLoading,
|
||||||
} from 'selectors/file_info'
|
} from "selectors/file_info";
|
||||||
import {
|
import { doCloseModal } from "actions/app";
|
||||||
doCloseModal,
|
|
||||||
} from 'actions/app'
|
|
||||||
|
|
||||||
const {
|
const { shell } = require("electron");
|
||||||
shell,
|
|
||||||
} = require('electron')
|
|
||||||
|
|
||||||
export function doFetchFileInfo(uri) {
|
export function doFetchFileInfo(uri) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
const claim = selectClaimsByUri(state)[uri]
|
const claim = selectClaimsByUri(state)[uri];
|
||||||
const outpoint = claim ? `${claim.txid}:${claim.nout}` : null
|
const outpoint = claim ? `${claim.txid}:${claim.nout}` : null;
|
||||||
const alreadyFetching = !!selectUrisLoading(state)[uri]
|
const alreadyFetching = !!selectUrisLoading(state)[uri];
|
||||||
|
|
||||||
if (!alreadyFetching) {
|
if (!alreadyFetching) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FETCH_FILE_INFO_STARTED,
|
type: types.FETCH_FILE_INFO_STARTED,
|
||||||
data: {
|
data: {
|
||||||
outpoint,
|
outpoint,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
lbry.file_list({outpoint: outpoint, full_status: true}).then(fileInfos => {
|
lbry
|
||||||
|
.file_list({ outpoint: outpoint, full_status: true })
|
||||||
dispatch({
|
.then(fileInfos => {
|
||||||
type: types.FETCH_FILE_INFO_COMPLETED,
|
dispatch({
|
||||||
data: {
|
type: types.FETCH_FILE_INFO_COMPLETED,
|
||||||
outpoint,
|
data: {
|
||||||
fileInfo: fileInfos && fileInfos.length ? fileInfos[0] : null,
|
outpoint,
|
||||||
}
|
fileInfo: fileInfos && fileInfos.length ? fileInfos[0] : null,
|
||||||
})
|
},
|
||||||
})
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doFileList() {
|
export function doFileList() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
const isPending = selectFileListIsPending(state)
|
const isPending = selectFileListIsPending(state);
|
||||||
|
|
||||||
if (!isPending) {
|
if (!isPending) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FILE_LIST_STARTED,
|
type: types.FILE_LIST_STARTED,
|
||||||
})
|
});
|
||||||
|
|
||||||
lbry.file_list().then((fileInfos) => {
|
lbry.file_list().then(fileInfos => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FILE_LIST_COMPLETED,
|
type: types.FILE_LIST_COMPLETED,
|
||||||
data: {
|
data: {
|
||||||
fileInfos,
|
fileInfos,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doOpenFileInShell(fileInfo) {
|
export function doOpenFileInShell(fileInfo) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
shell.openItem(fileInfo.download_path)
|
shell.openItem(fileInfo.download_path);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doOpenFileInFolder(fileInfo) {
|
export function doOpenFileInFolder(fileInfo) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
shell.showItemInFolder(fileInfo.download_path)
|
shell.showItemInFolder(fileInfo.download_path);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doDeleteFile(outpoint, deleteFromComputer) {
|
export function doDeleteFile(outpoint, deleteFromComputer) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FILE_DELETE,
|
type: types.FILE_DELETE,
|
||||||
data: {
|
data: {
|
||||||
outpoint
|
outpoint,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
lbry.file_delete({
|
lbry.file_delete({
|
||||||
outpoint: outpoint,
|
outpoint: outpoint,
|
||||||
delete_target_file: deleteFromComputer,
|
delete_target_file: deleteFromComputer,
|
||||||
})
|
});
|
||||||
|
|
||||||
dispatch(doCloseModal())
|
dispatch(doCloseModal());
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function doFetchFileInfosAndPublishedClaims() {
|
export function doFetchFileInfosAndPublishedClaims() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState(),
|
const state = getState(),
|
||||||
isClaimListMinePending = selectClaimListMineIsPending(state),
|
isClaimListMinePending = selectClaimListMineIsPending(state),
|
||||||
isFileInfoListPending = selectFileListIsPending(state)
|
isFileInfoListPending = selectFileListIsPending(state);
|
||||||
|
|
||||||
if (isClaimListMinePending === undefined) {
|
if (isClaimListMinePending === undefined) {
|
||||||
dispatch(doFetchClaimListMine())
|
dispatch(doFetchClaimListMine());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFileInfoListPending === undefined) {
|
if (isFileInfoListPending === undefined) {
|
||||||
dispatch(doFileList())
|
dispatch(doFileList());
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,36 +1,116 @@
|
||||||
import * as types from 'constants/action_types'
|
import * as types from "constants/action_types";
|
||||||
import lbry from 'lbry'
|
import lbry from "lbry";
|
||||||
import lbryio from 'lbryio';
|
import lbryio from "lbryio";
|
||||||
import rewards from 'rewards'
|
import rewards from "rewards";
|
||||||
|
import { selectRewards, selectRewardsByType } from "selectors/rewards";
|
||||||
|
|
||||||
export function doFetchRewards() {
|
export function doRewardList() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FETCH_REWARDS_STARTED,
|
type: types.FETCH_REWARDS_STARTED,
|
||||||
})
|
|
||||||
|
|
||||||
lbryio.call('reward', 'list', {}).then(function(userRewards) {
|
|
||||||
dispatch({
|
|
||||||
type: types.FETCH_REWARDS_COMPLETED,
|
|
||||||
data: { userRewards }
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
lbryio
|
||||||
|
.call("reward", "list", {})
|
||||||
|
.then(userRewards => {
|
||||||
|
dispatch({
|
||||||
|
type: types.FETCH_REWARDS_COMPLETED,
|
||||||
|
data: { userRewards },
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
dispatch({
|
||||||
|
type: types.FETCH_REWARDS_COMPLETED,
|
||||||
|
data: { userRewards: [] },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doClaimReward(rewardType) {
|
export function doClaimRewardType(rewardType) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
try {
|
const rewardsByType = selectRewardsByType(getState()),
|
||||||
rewards.claimReward(rewards[rewardType])
|
reward = rewardsByType[rewardType];
|
||||||
dispatch({
|
|
||||||
type: types.REWARD_CLAIMED,
|
if (reward) {
|
||||||
data: {
|
dispatch(doClaimReward(reward));
|
||||||
reward: rewards[rewardType]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch(err) {
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doClaimReward(reward, saveError = false) {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
if (reward.transaction_id) {
|
||||||
|
//already claimed, do nothing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: types.CLAIM_REWARD_STARTED,
|
||||||
|
data: { reward },
|
||||||
|
});
|
||||||
|
|
||||||
|
const success = reward => {
|
||||||
|
dispatch({
|
||||||
|
type: types.CLAIM_REWARD_SUCCESS,
|
||||||
|
data: {
|
||||||
|
reward,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const failure = error => {
|
||||||
|
dispatch({
|
||||||
|
type: types.CLAIM_REWARD_FAILURE,
|
||||||
|
data: {
|
||||||
|
reward,
|
||||||
|
error: saveError ? error : null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
rewards.claimReward(reward.reward_type).then(success, failure);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doClaimEligiblePurchaseRewards() {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
if (!lbryio.enabled || !lbryio.getAccessToken()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rewardsByType = selectRewardsByType(getState());
|
||||||
|
|
||||||
|
let types = {};
|
||||||
|
|
||||||
|
types[rewards.TYPE_FIRST_STREAM] = false;
|
||||||
|
types[rewards.TYPE_FEATURED_DOWNLOAD] = false;
|
||||||
|
types[rewards.TYPE_MANY_DOWNLOADS] = false;
|
||||||
|
Object.values(rewardsByType).forEach(reward => {
|
||||||
|
if (types[reward.reward_type] === false && reward.transaction_id) {
|
||||||
|
types[reward.reward_type] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let unclaimedType = Object.keys(types).find(type => {
|
||||||
|
return types[type] === false && type !== rewards.TYPE_FEATURED_DOWNLOAD; //handled below
|
||||||
|
});
|
||||||
|
if (unclaimedType) {
|
||||||
|
dispatch(doClaimRewardType(unclaimedType));
|
||||||
|
}
|
||||||
|
if (types[rewards.TYPE_FEATURED_DOWNLOAD] === false) {
|
||||||
|
dispatch(doClaimRewardType(rewards.TYPE_FEATURED_DOWNLOAD));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doClaimRewardClearError(reward) {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
dispatch({
|
||||||
|
type: types.CLAIM_REWARD_CLEAR_ERROR,
|
||||||
|
data: { reward },
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,28 @@
|
||||||
import * as types from 'constants/action_types'
|
import * as types from "constants/action_types";
|
||||||
import lbryuri from 'lbryuri'
|
import lbryuri from "lbryuri";
|
||||||
import lighthouse from 'lighthouse'
|
import lighthouse from "lighthouse";
|
||||||
import {
|
import { doResolveUri } from "actions/content";
|
||||||
doResolveUri,
|
import { doNavigate, doHistoryPush } from "actions/app";
|
||||||
} from 'actions/content'
|
import { selectCurrentPage } from "selectors/app";
|
||||||
import {
|
|
||||||
doNavigate,
|
|
||||||
doHistoryPush
|
|
||||||
} from 'actions/app'
|
|
||||||
import {
|
|
||||||
selectCurrentPage,
|
|
||||||
} from 'selectors/app'
|
|
||||||
|
|
||||||
export function doSearch(query) {
|
export function doSearch(query) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
const page = selectCurrentPage(state)
|
const page = selectCurrentPage(state);
|
||||||
|
|
||||||
if (!query) {
|
if (!query) {
|
||||||
return dispatch({
|
return dispatch({
|
||||||
type: types.SEARCH_CANCELLED,
|
type: types.SEARCH_CANCELLED,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.SEARCH_STARTED,
|
type: types.SEARCH_STARTED,
|
||||||
data: { query }
|
data: { query },
|
||||||
})
|
});
|
||||||
|
|
||||||
if(page != 'search') {
|
if (page != "search") {
|
||||||
dispatch(doNavigate('search', { query: query }))
|
dispatch(doNavigate("search", { query: query }));
|
||||||
} else {
|
} else {
|
||||||
lighthouse.search(query).then(results => {
|
lighthouse.search(query).then(results => {
|
||||||
results.forEach(result => {
|
results.forEach(result => {
|
||||||
|
@ -37,18 +30,18 @@ export function doSearch(query) {
|
||||||
channelName: result.channel_name,
|
channelName: result.channel_name,
|
||||||
contentName: result.name,
|
contentName: result.name,
|
||||||
claimId: result.channel_id || result.claim_id,
|
claimId: result.channel_id || result.claim_id,
|
||||||
})
|
});
|
||||||
dispatch(doResolveUri(uri))
|
dispatch(doResolveUri(uri));
|
||||||
})
|
});
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.SEARCH_COMPLETED,
|
type: types.SEARCH_COMPLETED,
|
||||||
data: {
|
data: {
|
||||||
query,
|
query,
|
||||||
results,
|
results,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,31 @@
|
||||||
import * as types from 'constants/action_types'
|
import * as types from "constants/action_types";
|
||||||
import lbry from 'lbry'
|
import lbry from "lbry";
|
||||||
|
|
||||||
export function doFetchDaemonSettings() {
|
export function doFetchDaemonSettings() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
lbry.settings_get().then((settings) => {
|
lbry.settings_get().then(settings => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.DAEMON_SETTINGS_RECEIVED,
|
type: types.DAEMON_SETTINGS_RECEIVED,
|
||||||
data: {
|
data: {
|
||||||
settings
|
settings,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doSetDaemonSetting(key, value) {
|
export function doSetDaemonSetting(key, value) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
let settings = {};
|
let settings = {};
|
||||||
settings[key] = value;
|
settings[key] = value;
|
||||||
lbry.settings_set(settings).then(settings)
|
lbry.settings_set(settings).then(settings);
|
||||||
lbry.get_settings().then((settings) => {
|
lbry.settings_get().then(settings => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.DAEMON_SETTINGS_RECEIVED,
|
type: types.DAEMON_SETTINGS_RECEIVED,
|
||||||
data: {
|
data: {
|
||||||
settings
|
settings,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
132
ui/js/actions/user.js
Normal file
132
ui/js/actions/user.js
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
import * as types from "constants/action_types";
|
||||||
|
import lbryio from "lbryio";
|
||||||
|
import { setLocal } from "utils";
|
||||||
|
import { doRewardList } from "actions/rewards";
|
||||||
|
import { selectEmailToVerify } from "selectors/user";
|
||||||
|
|
||||||
|
export function doAuthenticate() {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
dispatch({
|
||||||
|
type: types.AUTHENTICATION_STARTED,
|
||||||
|
});
|
||||||
|
lbryio
|
||||||
|
.authenticate()
|
||||||
|
.then(user => {
|
||||||
|
dispatch({
|
||||||
|
type: types.AUTHENTICATION_SUCCESS,
|
||||||
|
data: { user },
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(doRewardList()); //FIXME - where should this happen?
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dispatch({
|
||||||
|
type: types.AUTHENTICATION_FAILURE,
|
||||||
|
data: { error },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doUserFetch() {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
dispatch({
|
||||||
|
type: types.USER_FETCH_STARTED,
|
||||||
|
});
|
||||||
|
lbryio.setCurrentUser(
|
||||||
|
user => {
|
||||||
|
dispatch({
|
||||||
|
type: types.USER_FETCH_SUCCESS,
|
||||||
|
data: { user },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
dispatch({
|
||||||
|
type: types.USER_FETCH_FAILURE,
|
||||||
|
data: { error },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doUserEmailNew(email) {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
dispatch({
|
||||||
|
type: types.USER_EMAIL_NEW_STARTED,
|
||||||
|
email: email,
|
||||||
|
});
|
||||||
|
lbryio.call("user_email", "new", { email }, "post").then(
|
||||||
|
() => {
|
||||||
|
dispatch({
|
||||||
|
type: types.USER_EMAIL_NEW_SUCCESS,
|
||||||
|
data: { email },
|
||||||
|
});
|
||||||
|
dispatch(doUserFetch());
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
if (
|
||||||
|
error.xhr &&
|
||||||
|
(error.xhr.status == 409 ||
|
||||||
|
error.message == "This email is already in use")
|
||||||
|
) {
|
||||||
|
dispatch({
|
||||||
|
type: types.USER_EMAIL_NEW_EXISTS,
|
||||||
|
data: { email },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dispatch({
|
||||||
|
type: types.USER_EMAIL_NEW_FAILURE,
|
||||||
|
data: { error: error.message },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doUserEmailDecline() {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
setLocal("user_email_declined", true);
|
||||||
|
dispatch({
|
||||||
|
type: types.USER_EMAIL_DECLINE,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doUserEmailVerify(verificationToken) {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
const email = selectEmailToVerify(getState());
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: types.USER_EMAIL_VERIFY_STARTED,
|
||||||
|
code: verificationToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const failure = error => {
|
||||||
|
dispatch({
|
||||||
|
type: types.USER_EMAIL_VERIFY_FAILURE,
|
||||||
|
data: { error: error.message },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
lbryio
|
||||||
|
.call(
|
||||||
|
"user_email",
|
||||||
|
"confirm",
|
||||||
|
{ verification_token: verificationToken, email: email },
|
||||||
|
"post"
|
||||||
|
)
|
||||||
|
.then(userEmail => {
|
||||||
|
if (userEmail.is_verified) {
|
||||||
|
dispatch({
|
||||||
|
type: types.USER_EMAIL_VERIFY_SUCCESS,
|
||||||
|
data: { email },
|
||||||
|
});
|
||||||
|
dispatch(doUserFetch());
|
||||||
|
} else {
|
||||||
|
failure(new Error("Your email is still not verified.")); //shouldn't happen?
|
||||||
|
}
|
||||||
|
}, failure);
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,125 +1,127 @@
|
||||||
import * as types from 'constants/action_types'
|
import * as types from "constants/action_types";
|
||||||
import lbry from 'lbry'
|
import lbry from "lbry";
|
||||||
import {
|
import {
|
||||||
selectDraftTransaction,
|
selectDraftTransaction,
|
||||||
selectDraftTransactionAmount,
|
selectDraftTransactionAmount,
|
||||||
selectBalance,
|
selectBalance,
|
||||||
} from 'selectors/wallet'
|
} from "selectors/wallet";
|
||||||
import {
|
import { doOpenModal } from "actions/app";
|
||||||
doOpenModal,
|
|
||||||
} from 'actions/app'
|
|
||||||
|
|
||||||
export function doUpdateBalance(balance) {
|
export function doUpdateBalance(balance) {
|
||||||
return {
|
return {
|
||||||
type: types.UPDATE_BALANCE,
|
type: types.UPDATE_BALANCE,
|
||||||
data: {
|
data: {
|
||||||
balance: balance
|
balance: balance,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doFetchTransactions() {
|
export function doFetchTransactions() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FETCH_TRANSACTIONS_STARTED
|
type: types.FETCH_TRANSACTIONS_STARTED,
|
||||||
})
|
});
|
||||||
|
|
||||||
lbry.call('get_transaction_history', {}, (results) => {
|
lbry.call("transaction_list", {}, results => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FETCH_TRANSACTIONS_COMPLETED,
|
type: types.FETCH_TRANSACTIONS_COMPLETED,
|
||||||
data: {
|
data: {
|
||||||
transactions: results
|
transactions: results,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doGetNewAddress() {
|
export function doGetNewAddress() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.GET_NEW_ADDRESS_STARTED
|
type: types.GET_NEW_ADDRESS_STARTED,
|
||||||
})
|
});
|
||||||
|
|
||||||
lbry.wallet_new_address().then(function(address) {
|
lbry.wallet_new_address().then(function(address) {
|
||||||
localStorage.setItem('wallet_address', address);
|
localStorage.setItem("wallet_address", address);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.GET_NEW_ADDRESS_COMPLETED,
|
type: types.GET_NEW_ADDRESS_COMPLETED,
|
||||||
data: { address }
|
data: { address },
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doCheckAddressIsMine(address) {
|
export function doCheckAddressIsMine(address) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.CHECK_ADDRESS_IS_MINE_STARTED
|
type: types.CHECK_ADDRESS_IS_MINE_STARTED,
|
||||||
})
|
});
|
||||||
|
|
||||||
lbry.checkAddressIsMine(address, (isMine) => {
|
lbry.checkAddressIsMine(address, isMine => {
|
||||||
if (!isMine) dispatch(doGetNewAddress())
|
if (!isMine) dispatch(doGetNewAddress());
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.CHECK_ADDRESS_IS_MINE_COMPLETED
|
type: types.CHECK_ADDRESS_IS_MINE_COMPLETED,
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doSendDraftTransaction() {
|
export function doSendDraftTransaction() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState()
|
const state = getState();
|
||||||
const draftTx = selectDraftTransaction(state)
|
const draftTx = selectDraftTransaction(state);
|
||||||
const balance = selectBalance(state)
|
const balance = selectBalance(state);
|
||||||
const amount = selectDraftTransactionAmount(state)
|
const amount = selectDraftTransactionAmount(state);
|
||||||
|
|
||||||
if (balance - amount < 1) {
|
if (balance - amount < 1) {
|
||||||
return dispatch(doOpenModal('insufficientBalance'))
|
return dispatch(doOpenModal("insufficientBalance"));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.SEND_TRANSACTION_STARTED,
|
type: types.SEND_TRANSACTION_STARTED,
|
||||||
})
|
});
|
||||||
|
|
||||||
const successCallback = (results) => {
|
const successCallback = results => {
|
||||||
if(results === true) {
|
if (results === true) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.SEND_TRANSACTION_COMPLETED,
|
type: types.SEND_TRANSACTION_COMPLETED,
|
||||||
})
|
});
|
||||||
dispatch(doOpenModal('transactionSuccessful'))
|
dispatch(doOpenModal("transactionSuccessful"));
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.SEND_TRANSACTION_FAILED,
|
type: types.SEND_TRANSACTION_FAILED,
|
||||||
data: { error: results }
|
data: { error: results },
|
||||||
})
|
});
|
||||||
dispatch(doOpenModal('transactionFailed'))
|
dispatch(doOpenModal("transactionFailed"));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const errorCallback = (error) => {
|
const errorCallback = error => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.SEND_TRANSACTION_FAILED,
|
type: types.SEND_TRANSACTION_FAILED,
|
||||||
data: { error: error.message }
|
data: { error: error.message },
|
||||||
})
|
});
|
||||||
dispatch(doOpenModal('transactionFailed'))
|
dispatch(doOpenModal("transactionFailed"));
|
||||||
}
|
};
|
||||||
|
|
||||||
lbry.sendToAddress(draftTx.amount, draftTx.address, successCallback, errorCallback);
|
lbry.sendToAddress(
|
||||||
}
|
draftTx.amount,
|
||||||
|
draftTx.address,
|
||||||
|
successCallback,
|
||||||
|
errorCallback
|
||||||
|
);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doSetDraftTransactionAmount(amount) {
|
export function doSetDraftTransactionAmount(amount) {
|
||||||
return {
|
return {
|
||||||
type: types.SET_DRAFT_TRANSACTION_AMOUNT,
|
type: types.SET_DRAFT_TRANSACTION_AMOUNT,
|
||||||
data: { amount }
|
data: { amount },
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doSetDraftTransactionAddress(address) {
|
export function doSetDraftTransactionAddress(address) {
|
||||||
return {
|
return {
|
||||||
type: types.SET_DRAFT_TRANSACTION_ADDRESS,
|
type: types.SET_DRAFT_TRANSACTION_ADDRESS,
|
||||||
data: { address }
|
data: { address },
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
31
ui/js/app.js
31
ui/js/app.js
|
@ -1,18 +1,31 @@
|
||||||
import store from 'store.js';
|
import store from 'store.js';
|
||||||
|
import lbry from './lbry.js';
|
||||||
|
|
||||||
const env = ENV;
|
const env = ENV;
|
||||||
const config = require(`./config/${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 logs = [];
|
const logs = [];
|
||||||
const app = {
|
const app = {
|
||||||
env: env,
|
env: env,
|
||||||
config: config,
|
config: config,
|
||||||
store: store,
|
store: store,
|
||||||
logs: logs,
|
i18n: i18n,
|
||||||
log: function(message) {
|
logs: logs,
|
||||||
console.log(message);
|
log: function(message) {
|
||||||
logs.push(message);
|
console.log(message);
|
||||||
}
|
logs.push(message);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.__ = i18n.__;
|
||||||
|
window.__n = i18n.__n;
|
||||||
|
|
||||||
global.app = app;
|
global.app = app;
|
||||||
module.exports = app;
|
module.exports = app;
|
|
@ -1,26 +1,19 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { connect } from 'react-redux'
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import {
|
import { selectCurrentModal } from "selectors/app";
|
||||||
selectCurrentModal,
|
import { doCheckUpgradeAvailable, doAlertError } from "actions/app";
|
||||||
} from 'selectors/app'
|
import { doUpdateBalance } from "actions/wallet";
|
||||||
import {
|
import App from "./view";
|
||||||
doCheckUpgradeAvailable,
|
|
||||||
doAlertError,
|
|
||||||
} from 'actions/app'
|
|
||||||
import {
|
|
||||||
doUpdateBalance,
|
|
||||||
} from 'actions/wallet'
|
|
||||||
import App from './view'
|
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = state => ({
|
||||||
modal: selectCurrentModal(state),
|
modal: selectCurrentModal(state),
|
||||||
})
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
alertError: (errorList) => dispatch(doAlertError(errorList)),
|
alertError: errorList => dispatch(doAlertError(errorList)),
|
||||||
checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()),
|
checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()),
|
||||||
updateBalance: (balance) => dispatch(doUpdateBalance(balance))
|
updateBalance: balance => dispatch(doUpdateBalance(balance)),
|
||||||
})
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(App)
|
export default connect(select, perform)(App);
|
||||||
|
|
|
@ -1,42 +1,44 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import Router from 'component/router'
|
import Router from "component/router";
|
||||||
import Header from 'component/header';
|
import Header from "component/header";
|
||||||
import ErrorModal from 'component/errorModal'
|
import ErrorModal from "component/errorModal";
|
||||||
import DownloadingModal from 'component/downloadingModal'
|
import DownloadingModal from "component/downloadingModal";
|
||||||
import UpgradeModal from 'component/upgradeModal'
|
import UpgradeModal from "component/upgradeModal";
|
||||||
import lbry from 'lbry'
|
import WelcomeModal from "component/welcomeModal";
|
||||||
import {Line} from 'rc-progress'
|
import lbry from "lbry";
|
||||||
|
import { Line } from "rc-progress";
|
||||||
|
|
||||||
class App extends React.Component {
|
class App extends React.PureComponent {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
document.addEventListener('unhandledError', (event) => {
|
document.addEventListener("unhandledError", event => {
|
||||||
this.props.alertError(event.detail);
|
this.props.alertError(event.detail);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.props.upgradeSkipped) {
|
if (!this.props.upgradeSkipped) {
|
||||||
this.props.checkUpgradeAvailable()
|
this.props.checkUpgradeAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
lbry.balanceSubscribe((balance) => {
|
lbry.balanceSubscribe(balance => {
|
||||||
this.props.updateBalance(balance)
|
this.props.updateBalance(balance);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { modal } = this.props;
|
||||||
modal,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
return <div id="window">
|
return (
|
||||||
<Header />
|
<div id="window">
|
||||||
<div id="main-content">
|
<Header />
|
||||||
<Router />
|
<div id="main-content">
|
||||||
|
<Router />
|
||||||
|
</div>
|
||||||
|
{modal == "upgrade" && <UpgradeModal />}
|
||||||
|
{modal == "downloading" && <DownloadingModal />}
|
||||||
|
{modal == "error" && <ErrorModal />}
|
||||||
|
{modal == "welcome" && <WelcomeModal />}
|
||||||
</div>
|
</div>
|
||||||
{modal == 'upgrade' && <UpgradeModal />}
|
);
|
||||||
{modal == 'downloading' && <DownloadingModal />}
|
|
||||||
{modal == 'error' && <ErrorModal />}
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App
|
export default App;
|
||||||
|
|
|
@ -1,327 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
rewardType: null,
|
|
||||||
email: '',
|
|
||||||
submitting: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEmailChanged(event) {
|
|
||||||
this.setState({
|
|
||||||
email: event.target.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onEmailSaved(email) {
|
|
||||||
this.props.setStage("confirm", { email: email })
|
|
||||||
}
|
|
||||||
|
|
||||||
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 });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
rewardType: null,
|
|
||||||
code: '',
|
|
||||||
submitting: false,
|
|
||||||
errorMessage: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCodeChanged(event) {
|
|
||||||
this.setState({
|
|
||||||
code: event.target.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.setState({
|
|
||||||
submitting: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
hasReward: false,
|
|
||||||
rewardAmount: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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 <em>way different</em> 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 <em>LBC</em>.</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>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CodeRequiredStage extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this._balanceSubscribeId = null
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
balance: 0,
|
|
||||||
address: getLocal('wallet_address')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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 });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class AuthOverlay extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this._stages = {
|
|
||||||
pending: PendingStage,
|
|
||||||
error: ErrorStage,
|
|
||||||
nocode: CodeRequiredStage,
|
|
||||||
email: SubmitEmailStage,
|
|
||||||
confirm: ConfirmEmailStage,
|
|
||||||
welcome: WelcomeStage
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
stage: "pending",
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (!this.state.stage) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const StageContent = this._stages[this.state.stage];
|
|
||||||
|
|
||||||
if (!StageContent) {
|
|
||||||
return <span className="empty">Unknown authentication step.</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
this.state.stage != "welcome" ?
|
|
||||||
<ModalPage className="modal-page--full" isOpen={true} contentLabel="Authentication">
|
|
||||||
<h1>LBRY Early Access</h1>
|
|
||||||
<StageContent {...this.state.stageProps} setStage={(stage, stageProps) => { this.setStage(stage, stageProps) }} />
|
|
||||||
</ModalPage> :
|
|
||||||
<StageContent setStage={(stage, stageProps) => { this.setStage(stage, stageProps) }} {...this.state.stageProps} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
16
ui/js/component/auth/index.js
Normal file
16
ui/js/component/auth/index.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import {
|
||||||
|
selectAuthenticationIsPending,
|
||||||
|
selectEmailToVerify,
|
||||||
|
selectUserIsVerificationCandidate,
|
||||||
|
} from "selectors/user";
|
||||||
|
import Auth from "./view";
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
isPending: selectAuthenticationIsPending(state),
|
||||||
|
email: selectEmailToVerify(state),
|
||||||
|
isVerificationCandidate: selectUserIsVerificationCandidate(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, null)(Auth);
|
22
ui/js/component/auth/view.jsx
Normal file
22
ui/js/component/auth/view.jsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import React from "react";
|
||||||
|
import { BusyMessage } from "component/common";
|
||||||
|
import UserEmailNew from "component/userEmailNew";
|
||||||
|
import UserEmailVerify from "component/userEmailVerify";
|
||||||
|
|
||||||
|
export class Auth extends React.PureComponent {
|
||||||
|
render() {
|
||||||
|
const { isPending, email, isVerificationCandidate } = this.props;
|
||||||
|
|
||||||
|
if (isPending) {
|
||||||
|
return <BusyMessage message={__("Authenticating")} />;
|
||||||
|
} else if (!email) {
|
||||||
|
return <UserEmailNew />;
|
||||||
|
} else if (isVerificationCandidate) {
|
||||||
|
return <UserEmailVerify />;
|
||||||
|
} else {
|
||||||
|
return <span className="empty">{__("No further steps.")}</span>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Auth;
|
24
ui/js/component/authOverlay/index.jsx
Normal file
24
ui/js/component/authOverlay/index.jsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import React from "react";
|
||||||
|
import * as modal from "constants/modal_types";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { doUserEmailDecline } from "actions/user";
|
||||||
|
import { doOpenModal } from "actions/app";
|
||||||
|
import {
|
||||||
|
selectAuthenticationIsPending,
|
||||||
|
selectUserHasEmail,
|
||||||
|
selectUserIsAuthRequested,
|
||||||
|
} from "selectors/user";
|
||||||
|
import AuthOverlay from "./view";
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
hasEmail: selectUserHasEmail(state),
|
||||||
|
isPending: selectAuthenticationIsPending(state),
|
||||||
|
isShowing: selectUserIsAuthRequested(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
userEmailDecline: () => dispatch(doUserEmailDecline()),
|
||||||
|
openWelcomeModal: () => dispatch(doOpenModal(modal.WELCOME)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(AuthOverlay);
|
83
ui/js/component/authOverlay/view.jsx
Normal file
83
ui/js/component/authOverlay/view.jsx
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import React from "react";
|
||||||
|
import lbryio from "lbryio.js";
|
||||||
|
import ModalPage from "component/modal-page.js";
|
||||||
|
import Auth from "component/auth";
|
||||||
|
import Link from "component/link";
|
||||||
|
|
||||||
|
export class AuthOverlay extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
showNoEmailConfirm: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (this.props.isShowing && !this.props.isPending && !nextProps.isShowing) {
|
||||||
|
setTimeout(() => this.props.openWelcomeModal(), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onEmailSkipClick() {
|
||||||
|
this.setState({ showNoEmailConfirm: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onEmailSkipConfirm() {
|
||||||
|
this.props.userEmailDecline();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!lbryio.enabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { isPending, isShowing, hasEmail } = this.props;
|
||||||
|
|
||||||
|
if (isShowing) {
|
||||||
|
return (
|
||||||
|
<ModalPage
|
||||||
|
className="modal-page--full"
|
||||||
|
isOpen={true}
|
||||||
|
contentLabel="Authentication"
|
||||||
|
>
|
||||||
|
<h1>LBRY Early Access</h1>
|
||||||
|
<Auth />
|
||||||
|
{isPending
|
||||||
|
? ""
|
||||||
|
: <div className="form-row-submit">
|
||||||
|
{!hasEmail && this.state.showNoEmailConfirm
|
||||||
|
? <div className="help form-input-width">
|
||||||
|
<p>
|
||||||
|
{__(
|
||||||
|
"If you continue without an email, you will be ineligible to earn free LBC rewards, as well as unable to receive security related communications."
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
onClick={() => {
|
||||||
|
this.onEmailSkipConfirm();
|
||||||
|
}}
|
||||||
|
label={__("Continue without email")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
: <Link
|
||||||
|
className={"button-text-help"}
|
||||||
|
onClick={() => {
|
||||||
|
hasEmail
|
||||||
|
? this.onEmailSkipConfirm()
|
||||||
|
: this.onEmailSkipClick();
|
||||||
|
}}
|
||||||
|
label={
|
||||||
|
hasEmail ? __("Skip for now") : __("Do I have to?")
|
||||||
|
}
|
||||||
|
/>}
|
||||||
|
</div>}
|
||||||
|
</ModalPage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthOverlay;
|
|
@ -1,76 +1,95 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import lbry from '../lbry.js';
|
import lbry from "../lbry.js";
|
||||||
|
|
||||||
//component/icon.js
|
//component/icon.js
|
||||||
export class Icon extends React.Component {
|
export class Icon extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
icon: React.PropTypes.string.isRequired,
|
icon: React.PropTypes.string.isRequired,
|
||||||
className: React.PropTypes.string,
|
className: React.PropTypes.string,
|
||||||
fixed: React.PropTypes.bool,
|
fixed: React.PropTypes.bool,
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {fixed, className} = this.props;
|
const { fixed, className } = this.props;
|
||||||
const spanClassName = ('icon ' + ('fixed' in this.props ? 'icon-fixed-width ' : '') +
|
const spanClassName =
|
||||||
this.props.icon + ' ' + (this.props.className || ''));
|
"icon " +
|
||||||
return <span className={spanClassName}></span>
|
("fixed" in this.props ? "icon-fixed-width " : "") +
|
||||||
|
this.props.icon +
|
||||||
|
" " +
|
||||||
|
(this.props.className || "");
|
||||||
|
return <span className={spanClassName} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TruncatedText extends React.Component {
|
export class TruncatedText extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
lines: React.PropTypes.number,
|
lines: React.PropTypes.number,
|
||||||
}
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
lines: null
|
lines: null,
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <span className="truncated-text" style={{ WebkitLineClamp: this.props.lines }}>{this.props.children}</span>;
|
return (
|
||||||
|
<span
|
||||||
|
className="truncated-text"
|
||||||
|
style={{ WebkitLineClamp: this.props.lines }}
|
||||||
|
>
|
||||||
|
{this.props.children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BusyMessage extends React.Component {
|
export class BusyMessage extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
message: React.PropTypes.string,
|
message: React.PropTypes.string,
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <span>{this.props.message} <span className="busy-indicator"></span></span>
|
return (
|
||||||
|
<span>{this.props.message} <span className="busy-indicator" /></span>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CurrencySymbol extends React.Component {
|
export class CurrencySymbol extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
return <span>LBC</span>;
|
return <span>LBC</span>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CreditAmount extends React.Component {
|
export class CreditAmount extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
amount: React.PropTypes.number.isRequired,
|
amount: React.PropTypes.number.isRequired,
|
||||||
precision: React.PropTypes.number,
|
precision: React.PropTypes.number,
|
||||||
isEstimate: React.PropTypes.bool,
|
isEstimate: React.PropTypes.bool,
|
||||||
label: React.PropTypes.bool,
|
label: React.PropTypes.bool,
|
||||||
showFree: React.PropTypes.bool,
|
showFree: React.PropTypes.bool,
|
||||||
look: React.PropTypes.oneOf(['indicator', 'plain']),
|
look: React.PropTypes.oneOf(["indicator", "plain"]),
|
||||||
}
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
precision: 1,
|
precision: 1,
|
||||||
label: true,
|
label: true,
|
||||||
showFree: false,
|
showFree: false,
|
||||||
look: 'indicator',
|
look: "indicator",
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const formattedAmount = lbry.formatCredits(this.props.amount, this.props.precision);
|
const formattedAmount = lbry.formatCredits(
|
||||||
|
this.props.amount,
|
||||||
|
this.props.precision
|
||||||
|
);
|
||||||
let amountText;
|
let amountText;
|
||||||
if (this.props.showFree && parseFloat(formattedAmount) == 0) {
|
if (this.props.showFree && parseFloat(formattedAmount) == 0) {
|
||||||
amountText = 'free';
|
amountText = __("free");
|
||||||
} else if (this.props.label) {
|
} else if (this.props.label) {
|
||||||
amountText = formattedAmount + (parseFloat(formattedAmount) == 1 ? ' credit' : ' credits');
|
amountText =
|
||||||
|
formattedAmount +
|
||||||
|
" " +
|
||||||
|
(parseFloat(formattedAmount) == 1 ? __("credit") : __("credits"));
|
||||||
} else {
|
} else {
|
||||||
amountText = formattedAmount;
|
amountText = formattedAmount;
|
||||||
}
|
}
|
||||||
|
@ -80,19 +99,27 @@ export class CreditAmount extends React.Component {
|
||||||
<span>
|
<span>
|
||||||
{amountText}
|
{amountText}
|
||||||
</span>
|
</span>
|
||||||
{ this.props.isEstimate ? <span className="credit-amount__estimate" title="This is an estimate and does not include data fees">*</span> : null }
|
{this.props.isEstimate
|
||||||
|
? <span
|
||||||
|
className="credit-amount__estimate"
|
||||||
|
title={__("This is an estimate and does not include data fees")}
|
||||||
|
>
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
: null}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let addressStyle = {
|
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 {
|
export class Address extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
address: React.PropTypes.string,
|
address: React.PropTypes.string,
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -102,16 +129,27 @@ export class Address extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<input className="input-copyable" type="text" ref={(input) => { this._inputElem = input; }}
|
<input
|
||||||
onFocus={() => { this._inputElem.select(); }} style={addressStyle} readOnly="readonly" value={this.props.address}></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 {
|
export class Thumbnail extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
src: React.PropTypes.string,
|
src: React.PropTypes.string,
|
||||||
}
|
};
|
||||||
|
|
||||||
handleError() {
|
handleError() {
|
||||||
if (this.state.imageUrl != this._defaultImageUri) {
|
if (this.state.imageUrl != this._defaultImageUri) {
|
||||||
|
@ -124,9 +162,9 @@ export class Thumbnail extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this._defaultImageUri = lbry.imagePath('default-thumb.svg')
|
this._defaultImageUri = lbry.imagePath("default-thumb.svg");
|
||||||
this._maxLoadTime = 10000
|
this._maxLoadTime = 10000;
|
||||||
this._isMounted = false
|
this._isMounted = false;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
imageUri: this.props.src || this._defaultImageUri,
|
imageUri: this.props.src || this._defaultImageUri,
|
||||||
|
@ -149,9 +187,19 @@ export class Thumbnail extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const className = this.props.className ? this.props.className : '',
|
const className = this.props.className ? this.props.className : "",
|
||||||
otherProps = Object.assign({}, this.props)
|
otherProps = Object.assign({}, this.props);
|
||||||
delete otherProps.className;
|
delete otherProps.className;
|
||||||
return <img ref="img" onError={() => { this.handleError() }} {...otherProps} className={className} src={this.state.imageUri} />
|
return (
|
||||||
|
<img
|
||||||
|
ref="img"
|
||||||
|
onError={() => {
|
||||||
|
this.handleError();
|
||||||
|
}}
|
||||||
|
{...otherProps}
|
||||||
|
className={className}
|
||||||
|
src={this.state.imageUri}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,17 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect
|
import { doStartUpgrade, doCancelUpgrade } from "actions/app";
|
||||||
} from 'react-redux'
|
import { selectDownloadProgress, selectDownloadComplete } from "selectors/app";
|
||||||
import {
|
import DownloadingModal from "./view";
|
||||||
doStartUpgrade,
|
|
||||||
doCancelUpgrade,
|
|
||||||
} from 'actions/app'
|
|
||||||
import {
|
|
||||||
selectDownloadProgress,
|
|
||||||
selectDownloadComplete,
|
|
||||||
} from 'selectors/app'
|
|
||||||
import DownloadingModal from './view'
|
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = state => ({
|
||||||
downloadProgress: selectDownloadProgress(state),
|
downloadProgress: selectDownloadProgress(state),
|
||||||
downloadComplete: selectDownloadComplete(state),
|
downloadComplete: selectDownloadComplete(state),
|
||||||
})
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
startUpgrade: () => dispatch(doStartUpgrade()),
|
startUpgrade: () => dispatch(doStartUpgrade()),
|
||||||
cancelUpgrade: () => dispatch(doCancelUpgrade())
|
cancelUpgrade: () => dispatch(doCancelUpgrade()),
|
||||||
})
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(DownloadingModal)
|
export default connect(select, perform)(DownloadingModal);
|
||||||
|
|
|
@ -1,40 +1,62 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { Modal } from "component/modal";
|
||||||
Modal
|
import { Line } from "rc-progress";
|
||||||
} from 'component/modal'
|
import Link from "component/link";
|
||||||
import {Line} from 'rc-progress';
|
|
||||||
import Link from 'component/link'
|
|
||||||
|
|
||||||
class DownloadingModal extends React.Component {
|
class DownloadingModal extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
downloadProgress,
|
downloadProgress,
|
||||||
downloadComplete,
|
downloadComplete,
|
||||||
startUpgrade,
|
startUpgrade,
|
||||||
cancelUpgrade,
|
cancelUpgrade,
|
||||||
} = this.props
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={true} contentLabel="Downloading Update" type="custom">
|
<Modal
|
||||||
Downloading Update{downloadProgress ? `: ${downloadProgress}%` : null}
|
isOpen={true}
|
||||||
<Line percent={downloadProgress ? downloadProgress : 0} strokeWidth="4"/>
|
contentLabel={__("Downloading Update")}
|
||||||
{downloadComplete ? (
|
type="custom"
|
||||||
<div>
|
>
|
||||||
<br />
|
{__("Downloading Update")}
|
||||||
<p>Click "Begin Upgrade" to start the upgrade process.</p>
|
{downloadProgress ? `: ${downloadProgress}%` : null}
|
||||||
<p>The app will close, and you will be prompted to install the latest version of LBRY.</p>
|
<Line
|
||||||
<p>After the install is complete, please reopen the app.</p>
|
percent={downloadProgress ? downloadProgress : 0}
|
||||||
</div>
|
strokeWidth="4"
|
||||||
) : null }
|
/>
|
||||||
|
{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">
|
<div className="modal__buttons">
|
||||||
{downloadComplete
|
{downloadComplete
|
||||||
? <Link button="primary" label="Begin Upgrade" className="modal__button" onClick={startUpgrade} />
|
? <Link
|
||||||
|
button="primary"
|
||||||
|
label={__("Begin Upgrade")}
|
||||||
|
className="modal__button"
|
||||||
|
onClick={startUpgrade}
|
||||||
|
/>
|
||||||
: null}
|
: null}
|
||||||
<Link button="alt" label="Cancel" className="modal__button" onClick={cancelUpgrade} />
|
<Link
|
||||||
|
button="alt"
|
||||||
|
label={__("Cancel")}
|
||||||
|
className="modal__button"
|
||||||
|
onClick={cancelUpgrade}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DownloadingModal
|
export default DownloadingModal;
|
||||||
|
|
|
@ -1,23 +1,16 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect
|
import { selectCurrentModal, selectModalExtraContent } from "selectors/app";
|
||||||
} from 'react-redux'
|
import { doCloseModal } from "actions/app";
|
||||||
import {
|
import ErrorModal from "./view";
|
||||||
selectCurrentModal,
|
|
||||||
selectModalExtraContent,
|
|
||||||
} from 'selectors/app'
|
|
||||||
import {
|
|
||||||
doCloseModal,
|
|
||||||
} from 'actions/app'
|
|
||||||
import ErrorModal from './view'
|
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = state => ({
|
||||||
modal: selectCurrentModal(state),
|
modal: selectCurrentModal(state),
|
||||||
error: selectModalExtraContent(state),
|
error: selectModalExtraContent(state),
|
||||||
})
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
closeModal: () => dispatch(doCloseModal())
|
closeModal: () => dispatch(doCloseModal()),
|
||||||
})
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(ErrorModal)
|
export default connect(select, perform)(ErrorModal);
|
||||||
|
|
|
@ -1,54 +1,63 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import lbry from 'lbry'
|
import lbry from "lbry";
|
||||||
import {
|
import { ExpandableModal } from "component/modal";
|
||||||
ExpandableModal
|
|
||||||
} from 'component/modal'
|
|
||||||
|
|
||||||
class ErrorModal extends React.Component {
|
class ErrorModal extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { modal, closeModal, error } = this.props;
|
||||||
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 = {
|
const error_key_labels = {
|
||||||
connectionString: 'API connection string',
|
connectionString: __("API connection string"),
|
||||||
method: 'Method',
|
method: __("Method"),
|
||||||
params: 'Parameters',
|
params: __("Parameters"),
|
||||||
code: 'Error code',
|
code: __("Error code"),
|
||||||
message: 'Error message',
|
message: __("Error message"),
|
||||||
data: 'Error data',
|
data: __("Error data"),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const errorInfoList = [];
|
||||||
const errorInfoList = []
|
|
||||||
for (let key of Object.keys(error)) {
|
for (let key of Object.keys(error)) {
|
||||||
let val = typeof error[key] == 'string' ? error[key] : JSON.stringify(error[key]);
|
let val = typeof error[key] == "string"
|
||||||
|
? error[key]
|
||||||
|
: JSON.stringify(error[key]);
|
||||||
let label = error_key_labels[key];
|
let label = error_key_labels[key];
|
||||||
errorInfoList.push(<li key={key}><strong>{label}</strong>: <code>{val}</code></li>);
|
errorInfoList.push(
|
||||||
|
<li key={key}><strong>{label}</strong>: <code>{val}</code></li>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const errorInfo = <ul className="error-modal__error-list">{errorInfoList}</ul>
|
const errorInfo = (
|
||||||
|
<ul className="error-modal__error-list">{errorInfoList}</ul>
|
||||||
|
);
|
||||||
|
|
||||||
return(
|
return (
|
||||||
<ExpandableModal
|
<ExpandableModal
|
||||||
isOpen={modal == 'error'}
|
isOpen={modal == "error"}
|
||||||
contentLabel="Error" className="error-modal"
|
contentLabel={__("Error")}
|
||||||
|
className="error-modal"
|
||||||
overlayClassName="error-modal-overlay"
|
overlayClassName="error-modal-overlay"
|
||||||
onConfirmed={closeModal}
|
onConfirmed={closeModal}
|
||||||
extraContent={errorInfo}
|
extraContent={errorInfo}
|
||||||
>
|
>
|
||||||
<h3 className="modal__header">Error</h3>
|
<h3 className="modal__header">{__("Error")}</h3>
|
||||||
|
|
||||||
<div className="error-modal__content">
|
<div className="error-modal__content">
|
||||||
<div><img className="error-modal__warning-symbol" src={lbry.imagePath('warning.png')} /></div>
|
<div>
|
||||||
<p>We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.</p>
|
<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>
|
</div>
|
||||||
</ExpandableModal>
|
</ExpandableModal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ErrorModal
|
export default ErrorModal;
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
|
|
||||||
const {remote} = require('electron');
|
const { remote } = require("electron");
|
||||||
class FileSelector extends React.Component {
|
class FileSelector extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
type: React.PropTypes.oneOf(['file', 'directory']),
|
type: React.PropTypes.oneOf(["file", "directory"]),
|
||||||
initPath: React.PropTypes.string,
|
initPath: React.PropTypes.string,
|
||||||
onFileChosen: React.PropTypes.func,
|
onFileChosen: React.PropTypes.func,
|
||||||
}
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
type: 'file',
|
type: "file",
|
||||||
}
|
};
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -19,40 +19,46 @@ class FileSelector extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleButtonClick() {
|
handleButtonClick() {
|
||||||
remote.dialog.showOpenDialog({
|
remote.dialog.showOpenDialog(
|
||||||
properties: [this.props.type == 'file' ? 'openFile' : 'openDirectory'],
|
{
|
||||||
}, (paths) => {
|
properties: [this.props.type == "file" ? "openFile" : "openDirectory"],
|
||||||
if (!paths) { // User hit cancel, so do nothing
|
},
|
||||||
return;
|
paths => {
|
||||||
}
|
if (!paths) {
|
||||||
|
// User hit cancel, so do nothing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const path = paths[0];
|
const path = paths[0];
|
||||||
this.setState({
|
this.setState({
|
||||||
path: path,
|
path: path,
|
||||||
});
|
});
|
||||||
if (this.props.onFileChosen) {
|
if (this.props.onFileChosen) {
|
||||||
this.props.onFileChosen(path);
|
this.props.onFileChosen(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="file-selector">
|
<div className="file-selector">
|
||||||
<button type="button" className="file-selector__choose-button" onClick={() => this.handleButtonClick()}>
|
<button
|
||||||
{this.props.type == 'file' ?
|
type="button"
|
||||||
'Choose File' :
|
className="file-selector__choose-button"
|
||||||
'Choose Directory'}
|
onClick={() => this.handleButtonClick()}
|
||||||
|
>
|
||||||
|
{this.props.type == "file"
|
||||||
|
? __("Choose File")
|
||||||
|
: __("Choose Directory")}
|
||||||
</button>
|
</button>
|
||||||
{' '}
|
{" "}
|
||||||
<span className="file-selector__path">
|
<span className="file-selector__path">
|
||||||
{this.state.path ?
|
{this.state.path ? this.state.path : __("No File Chosen")}
|
||||||
this.state.path :
|
|
||||||
'No File Chosen'}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default FileSelector;
|
export default FileSelector;
|
||||||
|
|
|
@ -1,49 +1,30 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect,
|
import { selectPlatform } from "selectors/app";
|
||||||
} from 'react-redux'
|
|
||||||
import {
|
|
||||||
selectPlatform,
|
|
||||||
} from 'selectors/app'
|
|
||||||
import {
|
import {
|
||||||
makeSelectFileInfoForUri,
|
makeSelectFileInfoForUri,
|
||||||
makeSelectDownloadingForUri,
|
makeSelectDownloadingForUri,
|
||||||
makeSelectLoadingForUri,
|
makeSelectLoadingForUri,
|
||||||
} from 'selectors/file_info'
|
} from "selectors/file_info";
|
||||||
import {
|
import { makeSelectIsAvailableForUri } from "selectors/availability";
|
||||||
makeSelectIsAvailableForUri,
|
import { selectCurrentModal } from "selectors/app";
|
||||||
} from 'selectors/availability'
|
import { makeSelectCostInfoForUri } from "selectors/cost_info";
|
||||||
import {
|
import { doCloseModal, doOpenModal, doHistoryBack } from "actions/app";
|
||||||
selectCurrentModal,
|
import { doFetchAvailability } from "actions/availability";
|
||||||
} from 'selectors/app'
|
|
||||||
import {
|
|
||||||
makeSelectCostInfoForUri,
|
|
||||||
} from 'selectors/cost_info'
|
|
||||||
import {
|
|
||||||
doCloseModal,
|
|
||||||
doOpenModal,
|
|
||||||
doHistoryBack,
|
|
||||||
} from 'actions/app'
|
|
||||||
import {
|
|
||||||
doFetchAvailability
|
|
||||||
} from 'actions/availability'
|
|
||||||
import {
|
import {
|
||||||
doOpenFileInShell,
|
doOpenFileInShell,
|
||||||
doOpenFileInFolder,
|
doOpenFileInFolder,
|
||||||
doDeleteFile,
|
doDeleteFile,
|
||||||
} from 'actions/file_info'
|
} from "actions/file_info";
|
||||||
import {
|
import { doPurchaseUri, doLoadVideo } from "actions/content";
|
||||||
doPurchaseUri,
|
import FileActions from "./view";
|
||||||
doLoadVideo,
|
|
||||||
} from 'actions/content'
|
|
||||||
import FileActions from './view'
|
|
||||||
|
|
||||||
const makeSelect = () => {
|
const makeSelect = () => {
|
||||||
const selectFileInfoForUri = makeSelectFileInfoForUri()
|
const selectFileInfoForUri = makeSelectFileInfoForUri();
|
||||||
const selectIsAvailableForUri = makeSelectIsAvailableForUri()
|
const selectIsAvailableForUri = makeSelectIsAvailableForUri();
|
||||||
const selectDownloadingForUri = makeSelectDownloadingForUri()
|
const selectDownloadingForUri = makeSelectDownloadingForUri();
|
||||||
const selectCostInfoForUri = makeSelectCostInfoForUri()
|
const selectCostInfoForUri = makeSelectCostInfoForUri();
|
||||||
const selectLoadingForUri = makeSelectLoadingForUri()
|
const selectLoadingForUri = makeSelectLoadingForUri();
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
fileInfo: selectFileInfoForUri(state, props),
|
fileInfo: selectFileInfoForUri(state, props),
|
||||||
|
@ -53,23 +34,23 @@ const makeSelect = () => {
|
||||||
downloading: selectDownloadingForUri(state, props),
|
downloading: selectDownloadingForUri(state, props),
|
||||||
costInfo: selectCostInfoForUri(state, props),
|
costInfo: selectCostInfoForUri(state, props),
|
||||||
loading: selectLoadingForUri(state, props),
|
loading: selectLoadingForUri(state, props),
|
||||||
})
|
});
|
||||||
|
|
||||||
return select
|
return select;
|
||||||
}
|
};
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
checkAvailability: (uri) => dispatch(doFetchAvailability(uri)),
|
checkAvailability: uri => dispatch(doFetchAvailability(uri)),
|
||||||
closeModal: () => dispatch(doCloseModal()),
|
closeModal: () => dispatch(doCloseModal()),
|
||||||
openInFolder: (fileInfo) => dispatch(doOpenFileInFolder(fileInfo)),
|
openInFolder: fileInfo => dispatch(doOpenFileInFolder(fileInfo)),
|
||||||
openInShell: (fileInfo) => dispatch(doOpenFileInShell(fileInfo)),
|
openInShell: fileInfo => dispatch(doOpenFileInShell(fileInfo)),
|
||||||
deleteFile: (fileInfo, deleteFromComputer) => {
|
deleteFile: (fileInfo, deleteFromComputer) => {
|
||||||
dispatch(doHistoryBack())
|
dispatch(doHistoryBack());
|
||||||
dispatch(doDeleteFile(fileInfo, deleteFromComputer))
|
dispatch(doDeleteFile(fileInfo, deleteFromComputer));
|
||||||
},
|
},
|
||||||
openModal: (modal) => dispatch(doOpenModal(modal)),
|
openModal: modal => dispatch(doOpenModal(modal)),
|
||||||
startDownload: (uri) => dispatch(doPurchaseUri(uri, 'affirmPurchase')),
|
startDownload: uri => dispatch(doPurchaseUri(uri, "affirmPurchase")),
|
||||||
loadVideo: (uri) => dispatch(doLoadVideo(uri)),
|
loadVideo: uri => dispatch(doLoadVideo(uri)),
|
||||||
})
|
});
|
||||||
|
|
||||||
export default connect(makeSelect, perform)(FileActions)
|
export default connect(makeSelect, perform)(FileActions);
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import {Icon,BusyMessage} from 'component/common';
|
import { Icon, BusyMessage } from "component/common";
|
||||||
import FilePrice from 'component/filePrice'
|
import FilePrice from "component/filePrice";
|
||||||
import {Modal} from 'component/modal';
|
import { Modal } from "component/modal";
|
||||||
import {FormField} from 'component/form';
|
import { FormField } from "component/form";
|
||||||
import Link from 'component/link';
|
import Link from "component/link";
|
||||||
import {ToolTip} from 'component/tooltip';
|
import { ToolTip } from "component/tooltip";
|
||||||
import {DropDownMenu, DropDownMenuItem} from 'component/menu';
|
import { DropDownMenu, DropDownMenuItem } from "component/menu";
|
||||||
|
|
||||||
class FileActions extends React.Component {
|
class FileActions extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
forceShowActions: false,
|
forceShowActions: false,
|
||||||
deleteChecked: false,
|
deleteChecked: false,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.checkAvailability(this.props.uri)
|
this.checkAvailability(this.props.uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
this.checkAvailability(nextProps.uri)
|
this.checkAvailability(nextProps.uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAvailability(uri) {
|
checkAvailability(uri) {
|
||||||
if (!this._uri || uri !== this._uri) {
|
if (!this._uri || uri !== this._uri) {
|
||||||
this._uri = uri;
|
this._uri = uri;
|
||||||
this.props.checkAvailability(uri)
|
this.props.checkAvailability(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,12 +40,12 @@ class FileActions extends React.Component {
|
||||||
handleDeleteCheckboxClicked(event) {
|
handleDeleteCheckboxClicked(event) {
|
||||||
this.setState({
|
this.setState({
|
||||||
deleteChecked: event.target.checked,
|
deleteChecked: event.target.checked,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onAffirmPurchase() {
|
onAffirmPurchase() {
|
||||||
this.props.closeModal()
|
this.props.closeModal();
|
||||||
this.props.loadVideo(this.props.uri)
|
this.props.loadVideo(this.props.uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -64,88 +64,159 @@ class FileActions extends React.Component {
|
||||||
startDownload,
|
startDownload,
|
||||||
costInfo,
|
costInfo,
|
||||||
loading,
|
loading,
|
||||||
} = this.props
|
} = this.props;
|
||||||
|
|
||||||
const deleteChecked = this.state.deleteChecked,
|
const deleteChecked = this.state.deleteChecked,
|
||||||
metadata = fileInfo ? fileInfo.metadata : null,
|
metadata = fileInfo ? fileInfo.metadata : null,
|
||||||
openInFolderMessage = platform.startsWith('Mac') ? 'Open in Finder' : 'Open in Folder',
|
openInFolderMessage = platform.startsWith("Mac")
|
||||||
showMenu = fileInfo && Object.keys(fileInfo).length > 0,
|
? __("Open in Finder")
|
||||||
title = metadata ? metadata.title : uri;
|
: __("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
|
content = (
|
||||||
progress = (fileInfo && fileInfo.written_bytes) ? fileInfo.written_bytes / fileInfo.total_bytes * 100 : 0,
|
<div className="faux-button-block file-actions__download-status-bar button-set-item">
|
||||||
label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...',
|
<div
|
||||||
labelWithIcon = <span className="button__content"><Icon icon="icon-download" /><span>{label}</span></span>;
|
className="faux-button-block file-actions__download-status-bar-overlay"
|
||||||
|
style={{ width: progress + "%" }}
|
||||||
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}
|
||||||
{labelWithIcon}
|
</div>
|
||||||
</div>
|
{labelWithIcon}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
} else if (!fileInfo && isAvailable === undefined) {
|
} else if (!fileInfo && isAvailable === undefined) {
|
||||||
|
content = <BusyMessage message={__("Checking availability")} />;
|
||||||
content = <BusyMessage message="Checking availability" />
|
|
||||||
|
|
||||||
} else if (!fileInfo && !isAvailable && !this.state.forceShowActions) {
|
} else if (!fileInfo && !isAvailable && !this.state.forceShowActions) {
|
||||||
|
content = (
|
||||||
content = <div>
|
<div>
|
||||||
<div className="button-set-item empty">Content unavailable.</div>
|
<div className="button-set-item empty">
|
||||||
<ToolTip label="Why?"
|
{__("Content unavailable.")}
|
||||||
body="The content on LBRY is hosted by its users. It appears there are no users connected that have this file at the moment."
|
</div>
|
||||||
className="button-set-item" />
|
<ToolTip
|
||||||
<Link label="Try Anyway" onClick={this.onShowFileActionsRowClicked.bind(this)} className="button-text button-set-item" />
|
label={__("Why?")}
|
||||||
</div>
|
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) {
|
} else if (fileInfo === null && !downloading) {
|
||||||
if (!costInfo) {
|
if (!costInfo) {
|
||||||
content = <BusyMessage message="Fetching cost info" />
|
content = <BusyMessage message={__("Fetching cost info")} />;
|
||||||
} else {
|
} else {
|
||||||
content = <Link button="text" label="Download" icon="icon-download" onClick={() => { startDownload(uri) } } />;
|
content = (
|
||||||
|
<Link
|
||||||
|
button="text"
|
||||||
|
label={__("Download")}
|
||||||
|
icon="icon-download"
|
||||||
|
onClick={() => {
|
||||||
|
startDownload(uri);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (fileInfo && fileInfo.download_path) {
|
} else if (fileInfo && fileInfo.download_path) {
|
||||||
content = <Link label="Open" button="text" icon="icon-folder-open" onClick={() => openInShell(fileInfo)} />;
|
content = (
|
||||||
|
<Link
|
||||||
|
label={__("Open")}
|
||||||
|
button="text"
|
||||||
|
icon="icon-folder-open"
|
||||||
|
onClick={() => openInShell(fileInfo)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log('handle this case of file action props?');
|
console.log("handle this case of file action props?");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="file-actions">
|
<section className="file-actions">
|
||||||
{ content }
|
{content}
|
||||||
{ showMenu ?
|
{showMenu
|
||||||
<DropDownMenu>
|
? <DropDownMenu>
|
||||||
<DropDownMenuItem key={0} onClick={() => openInFolder(fileInfo)} label={openInFolderMessage} />
|
<DropDownMenuItem
|
||||||
<DropDownMenuItem key={1} onClick={() => openModal('confirmRemove')} label="Remove..." />
|
key={0}
|
||||||
</DropDownMenu> : '' }
|
onClick={() => openInFolder(fileInfo)}
|
||||||
<Modal type="confirm" isOpen={modal == 'affirmPurchase'}
|
label={openInFolderMessage}
|
||||||
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.
|
<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>
|
||||||
<Modal isOpen={modal == 'notEnoughCredits'} contentLabel="Not enough credits"
|
<Modal
|
||||||
onConfirmed={closeModal}>
|
isOpen={modal == "notEnoughCredits"}
|
||||||
You don't have enough LBRY credits to pay for this stream.
|
contentLabel={__("Not enough credits")}
|
||||||
|
onConfirmed={closeModal}
|
||||||
|
>
|
||||||
|
{__("You don't have enough LBRY credits to pay for this stream.")}
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal isOpen={modal == 'timedOut'} contentLabel="Download failed"
|
<Modal
|
||||||
onConfirmed={closeModal}>
|
isOpen={modal == "timedOut"}
|
||||||
LBRY was unable to download the stream <strong>{uri}</strong>.
|
contentLabel={__("Download failed")}
|
||||||
|
onConfirmed={closeModal}
|
||||||
|
>
|
||||||
|
{__("LBRY was unable to download the stream")} <strong>{uri}</strong>.
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal isOpen={modal == 'confirmRemove'}
|
<Modal
|
||||||
contentLabel="Not enough credits"
|
isOpen={modal == "confirmRemove"}
|
||||||
type="confirm"
|
contentLabel={__("Not enough credits")}
|
||||||
confirmButtonLabel="Remove"
|
type="confirm"
|
||||||
onConfirmed={() => deleteFile(fileInfo.outpoint, deleteChecked)}
|
confirmButtonLabel={__("Remove")}
|
||||||
onAborted={closeModal}>
|
onConfirmed={() => deleteFile(fileInfo.outpoint, deleteChecked)}
|
||||||
<p>Are you sure you'd like to remove <cite>{title}</cite> from LBRY?</p>
|
onAborted={closeModal}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
{__("Are you sure you'd like to remove")} <cite>{title}</cite>
|
||||||
|
{" "}{__("from LBRY?")}
|
||||||
|
</p>
|
||||||
|
|
||||||
<label><FormField type="checkbox" checked={deleteChecked} onClick={this.handleDeleteCheckboxClicked.bind(this)} /> Delete this file from my computer</label>
|
<label>
|
||||||
|
<FormField
|
||||||
|
type="checkbox"
|
||||||
|
checked={deleteChecked}
|
||||||
|
onClick={this.handleDeleteCheckboxClicked.bind(this)}
|
||||||
|
/>
|
||||||
|
{" "}{__("Delete this file from my computer")}
|
||||||
|
</label>
|
||||||
</Modal>
|
</Modal>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FileActions
|
export default FileActions;
|
||||||
|
|
|
@ -1,34 +1,21 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect
|
import { doNavigate } from "actions/app";
|
||||||
} from 'react-redux'
|
import { doResolveUri, doCancelResolveUri } from "actions/content";
|
||||||
import {
|
import { selectObscureNsfw } from "selectors/app";
|
||||||
doNavigate,
|
|
||||||
} from 'actions/app'
|
|
||||||
import {
|
|
||||||
doResolveUri,
|
|
||||||
doCancelResolveUri,
|
|
||||||
} from 'actions/content'
|
|
||||||
import {
|
|
||||||
selectObscureNsfw,
|
|
||||||
} from 'selectors/app'
|
|
||||||
import {
|
import {
|
||||||
makeSelectClaimForUri,
|
makeSelectClaimForUri,
|
||||||
makeSelectMetadataForUri,
|
makeSelectMetadataForUri,
|
||||||
} from 'selectors/claims'
|
} from "selectors/claims";
|
||||||
import {
|
import { makeSelectFileInfoForUri } from "selectors/file_info";
|
||||||
makeSelectFileInfoForUri,
|
import { makeSelectIsResolvingForUri } from "selectors/content";
|
||||||
} from 'selectors/file_info'
|
import FileCard from "./view";
|
||||||
import {
|
|
||||||
makeSelectIsResolvingForUri,
|
|
||||||
} from 'selectors/content'
|
|
||||||
import FileCard from './view'
|
|
||||||
|
|
||||||
const makeSelect = () => {
|
const makeSelect = () => {
|
||||||
const selectClaimForUri = makeSelectClaimForUri()
|
const selectClaimForUri = makeSelectClaimForUri();
|
||||||
const selectFileInfoForUri = makeSelectFileInfoForUri()
|
const selectFileInfoForUri = makeSelectFileInfoForUri();
|
||||||
const selectMetadataForUri = makeSelectMetadataForUri()
|
const selectMetadataForUri = makeSelectMetadataForUri();
|
||||||
const selectResolvingUri = makeSelectIsResolvingForUri()
|
const selectResolvingUri = makeSelectIsResolvingForUri();
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
claim: selectClaimForUri(state, props),
|
claim: selectClaimForUri(state, props),
|
||||||
|
@ -36,15 +23,15 @@ const makeSelect = () => {
|
||||||
obscureNsfw: selectObscureNsfw(state),
|
obscureNsfw: selectObscureNsfw(state),
|
||||||
metadata: selectMetadataForUri(state, props),
|
metadata: selectMetadataForUri(state, props),
|
||||||
isResolvingUri: selectResolvingUri(state, props),
|
isResolvingUri: selectResolvingUri(state, props),
|
||||||
})
|
});
|
||||||
|
|
||||||
return select
|
return select;
|
||||||
}
|
};
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||||
resolveUri: (uri) => dispatch(doResolveUri(uri)),
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
cancelResolveUri: (uri) => dispatch(doCancelResolveUri(uri))
|
cancelResolveUri: uri => dispatch(doCancelResolveUri(uri)),
|
||||||
})
|
});
|
||||||
|
|
||||||
export default connect(makeSelect, perform)(FileCard)
|
export default connect(makeSelect, perform)(FileCard);
|
||||||
|
|
|
@ -1,42 +1,33 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import lbry from 'lbry.js';
|
import lbry from "lbry.js";
|
||||||
import lbryuri from 'lbryuri.js';
|
import lbryuri from "lbryuri.js";
|
||||||
import Link from 'component/link';
|
import Link from "component/link";
|
||||||
import {Thumbnail, TruncatedText, Icon} from 'component/common';
|
import { Thumbnail, TruncatedText, Icon } from "component/common";
|
||||||
import FilePrice from 'component/filePrice'
|
import FilePrice from "component/filePrice";
|
||||||
import UriIndicator from 'component/uriIndicator';
|
import UriIndicator from "component/uriIndicator";
|
||||||
|
|
||||||
class FileCard extends React.Component {
|
class FileCard extends React.PureComponent {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.resolve(this.props)
|
this.resolve(this.props);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
this.resolve(nextProps)
|
this.resolve(nextProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(props) {
|
resolve(props) {
|
||||||
const {
|
const { isResolvingUri, resolveUri, claim, uri } = props;
|
||||||
isResolvingUri,
|
|
||||||
resolveUri,
|
|
||||||
claim,
|
|
||||||
uri,
|
|
||||||
} = props
|
|
||||||
|
|
||||||
if(!isResolvingUri && claim === undefined && uri) {
|
if (!isResolvingUri && claim === undefined && uri) {
|
||||||
resolveUri(uri)
|
resolveUri(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
const {
|
const { isResolvingUri, cancelResolveUri, uri } = this.props;
|
||||||
isResolvingUri,
|
|
||||||
cancelResolveUri,
|
|
||||||
uri
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
if (isResolvingUri) {
|
if (isResolvingUri) {
|
||||||
cancelResolveUri(uri)
|
cancelResolveUri(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,56 +44,75 @@ class FileCard extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 uri = lbryuri.normalize(this.props.uri);
|
||||||
const title = !isResolvingUri && metadata && metadata.title ? metadata.title : uri;
|
const title = !isResolvingUri && metadata && metadata.title
|
||||||
|
? metadata.title
|
||||||
|
: uri;
|
||||||
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
||||||
|
|
||||||
let description = ""
|
let description = "";
|
||||||
if (isResolvingUri) {
|
if (isResolvingUri) {
|
||||||
description = "Loading..."
|
description = __("Loading...");
|
||||||
} else if (metadata && metadata.description) {
|
} else if (metadata && metadata.description) {
|
||||||
description = metadata.description
|
description = metadata.description;
|
||||||
} else if (claim === null) {
|
} else if (claim === null) {
|
||||||
description = 'This address contains no content.'
|
description = __("This address contains no content.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={ 'card card--small card--link ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver.bind(this)} onMouseLeave={this.handleMouseOut.bind(this)}>
|
<section
|
||||||
|
className={
|
||||||
|
"card card--small card--link " +
|
||||||
|
(obscureNsfw ? "card--obscured " : "")
|
||||||
|
}
|
||||||
|
onMouseEnter={this.handleMouseOver.bind(this)}
|
||||||
|
onMouseLeave={this.handleMouseOut.bind(this)}
|
||||||
|
>
|
||||||
<div className="card__inner">
|
<div className="card__inner">
|
||||||
<Link onClick={() => navigate('/show', { uri })} className="card__link">
|
<Link
|
||||||
|
onClick={() => navigate("/show", { uri })}
|
||||||
|
className="card__link"
|
||||||
|
>
|
||||||
<div className="card__title-identity">
|
<div className="card__title-identity">
|
||||||
<h5 title={title}><TruncatedText lines={1}>{title}</TruncatedText></h5>
|
<h5 title={title}>
|
||||||
|
<TruncatedText lines={1}>{title}</TruncatedText>
|
||||||
|
</h5>
|
||||||
<div className="card__subtitle">
|
<div className="card__subtitle">
|
||||||
<span style={{float: "right"}}>
|
<span style={{ float: "right" }}>
|
||||||
<FilePrice uri={uri} />
|
<FilePrice uri={uri} />
|
||||||
{ fileInfo ? <span>{' '}<Icon fixed icon="icon-folder" /></span> : '' }
|
{fileInfo
|
||||||
|
? <span>{" "}<Icon fixed icon="icon-folder" /></span>
|
||||||
|
: ""}
|
||||||
</span>
|
</span>
|
||||||
<UriIndicator uri={uri} />
|
<UriIndicator uri={uri} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{metadata && metadata.thumbnail &&
|
{metadata &&
|
||||||
<div className="card__media" style={{ backgroundImage: "url('" + metadata.thumbnail + "')" }}></div>
|
metadata.thumbnail &&
|
||||||
}
|
<div
|
||||||
|
className="card__media"
|
||||||
|
style={{ backgroundImage: "url('" + metadata.thumbnail + "')" }}
|
||||||
|
/>}
|
||||||
<div className="card__content card__subtext card__subtext--two-lines">
|
<div className="card__content card__subtext card__subtext--two-lines">
|
||||||
<TruncatedText lines={2}>{description}</TruncatedText>
|
<TruncatedText lines={2}>{description}</TruncatedText>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
{obscureNsfw && this.state.hovered
|
{obscureNsfw && this.state.hovered
|
||||||
? <div className='card-overlay'>
|
? <div className="card-overlay">
|
||||||
<p>
|
<p>
|
||||||
This content is Not Safe For Work.
|
{__(
|
||||||
To view adult content, please change your <Link className="button-text" onClick={() => navigate('settings')} label="Settings" />.
|
"This content is Not Safe For Work. To view adult content, please change your"
|
||||||
</p>
|
)}
|
||||||
</div>
|
{" "}
|
||||||
|
<Link
|
||||||
|
className="button-text"
|
||||||
|
onClick={() => navigate("settings")}
|
||||||
|
label={__("Settings")}
|
||||||
|
/>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
: null}
|
: null}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -110,4 +120,4 @@ class FileCard extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FileCard
|
export default FileCard;
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect
|
import FileList from "./view";
|
||||||
} 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);
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import lbry from 'lbry.js';
|
import lbry from "lbry.js";
|
||||||
import lbryuri from 'lbryuri.js';
|
import lbryuri from "lbryuri.js";
|
||||||
import Link from 'component/link';
|
import Link from "component/link";
|
||||||
import {FormField} from 'component/form.js';
|
import { FormField } from "component/form.js";
|
||||||
import FileTile from 'component/fileTile';
|
import FileTile from "component/fileTile";
|
||||||
import rewards from 'rewards.js';
|
import rewards from "rewards.js";
|
||||||
import lbryio from 'lbryio.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 {
|
class FileList extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
sortBy: 'date',
|
sortBy: "date",
|
||||||
}
|
};
|
||||||
|
|
||||||
this._sortFunctions = {
|
this._sortFunctions = {
|
||||||
date: function(fileInfos) {
|
date: function(fileInfos) {
|
||||||
|
@ -22,8 +22,12 @@ class FileList extends React.Component {
|
||||||
},
|
},
|
||||||
title: function(fileInfos) {
|
title: function(fileInfos) {
|
||||||
return fileInfos.slice().sort(function(fileInfo1, fileInfo2) {
|
return fileInfos.slice().sort(function(fileInfo1, fileInfo2) {
|
||||||
const title1 = fileInfo1.metadata ? fileInfo1.metadata.stream.metadata.title.toLowerCase() : fileInfo1.name;
|
const title1 = fileInfo1.metadata
|
||||||
const title2 = fileInfo2.metadata ? fileInfo2.metadata.stream.metadata.title.toLowerCase() : fileInfo2.name;
|
? fileInfo1.metadata.stream.metadata.title.toLowerCase()
|
||||||
|
: fileInfo1.name;
|
||||||
|
const title2 = fileInfo2.metadata
|
||||||
|
? fileInfo2.metadata.stream.metadata.title.toLowerCase()
|
||||||
|
: fileInfo2.name;
|
||||||
if (title1 < title2) {
|
if (title1 < title2) {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (title1 > title2) {
|
} else if (title1 > title2) {
|
||||||
|
@ -31,63 +35,66 @@ class FileList extends React.Component {
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
filename: function(fileInfos) {
|
filename: function(fileInfos) {
|
||||||
return fileInfos.slice().sort(function({file_name: fileName1}, {file_name: fileName2}) {
|
return fileInfos
|
||||||
const fileName1Lower = fileName1.toLowerCase();
|
.slice()
|
||||||
const fileName2Lower = fileName2.toLowerCase();
|
.sort(function({ file_name: fileName1 }, { file_name: fileName2 }) {
|
||||||
if (fileName1Lower < fileName2Lower) {
|
const fileName1Lower = fileName1.toLowerCase();
|
||||||
return -1;
|
const fileName2Lower = fileName2.toLowerCase();
|
||||||
} else if (fileName2Lower > fileName1Lower) {
|
if (fileName1Lower < fileName2Lower) {
|
||||||
return 1;
|
return -1;
|
||||||
} else {
|
} else if (fileName2Lower > fileName1Lower) {
|
||||||
return 0;
|
return 1;
|
||||||
}
|
} else {
|
||||||
})
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSortChanged(event) {
|
handleSortChanged(event) {
|
||||||
this.setState({
|
this.setState({
|
||||||
sortBy: event.target.value,
|
sortBy: event.target.value,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { handleSortChanged, fetching, fileInfos } = this.props;
|
||||||
handleSortChanged,
|
const { sortBy } = this.state;
|
||||||
fetching,
|
const content = [];
|
||||||
fileInfos,
|
|
||||||
} = this.props
|
|
||||||
const {
|
|
||||||
sortBy,
|
|
||||||
} = this.state
|
|
||||||
const content = []
|
|
||||||
|
|
||||||
this._sortFunctions[sortBy](fileInfos).forEach(fileInfo => {
|
this._sortFunctions[sortBy](fileInfos).forEach(fileInfo => {
|
||||||
const uri = lbryuri.build({
|
const uri = lbryuri.build({
|
||||||
contentName: fileInfo.name,
|
contentName: fileInfo.name,
|
||||||
channelName: fileInfo.channel_name,
|
channelName: fileInfo.channel_name,
|
||||||
})
|
});
|
||||||
content.push(<FileTile key={uri} uri={uri} hidePrice={true} showEmpty={this.props.fileTileShowEmpty} />)
|
content.push(
|
||||||
})
|
<FileTile
|
||||||
|
key={uri}
|
||||||
|
uri={uri}
|
||||||
|
hidePrice={true}
|
||||||
|
showEmpty={this.props.fileTileShowEmpty}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<section className="file-list__header">
|
<section className="file-list__header">
|
||||||
{ fetching && <span className="busy-indicator"/> }
|
{fetching && <span className="busy-indicator" />}
|
||||||
<span className='sort-section'>
|
<span className="sort-section">
|
||||||
Sort by { ' ' }
|
{__("Sort by")} {" "}
|
||||||
<FormField type="select" onChange={this.handleSortChanged.bind(this)}>
|
<FormField type="select" onChange={this.handleSortChanged.bind(this)}>
|
||||||
<option value="date">Date</option>
|
<option value="date">{__("Date")}</option>
|
||||||
<option value="title">Title</option>
|
<option value="title">{__("Title")}</option>
|
||||||
<option value="filename">File name</option>
|
<option value="filename">{__("File name")}</option>
|
||||||
</FormField>
|
</FormField>
|
||||||
</span>
|
</span>
|
||||||
{content}
|
{content}
|
||||||
</section>
|
</section>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FileList
|
export default FileList;
|
||||||
|
|
|
@ -1,29 +1,23 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect,
|
import { doSearch } from "actions/search";
|
||||||
} from 'react-redux'
|
|
||||||
import {
|
|
||||||
doSearch,
|
|
||||||
} from 'actions/search'
|
|
||||||
import {
|
import {
|
||||||
selectIsSearching,
|
selectIsSearching,
|
||||||
selectCurrentSearchResults,
|
selectCurrentSearchResults,
|
||||||
selectSearchQuery,
|
selectSearchQuery,
|
||||||
} from 'selectors/search'
|
} from "selectors/search";
|
||||||
import {
|
import { doNavigate } from "actions/app";
|
||||||
doNavigate,
|
import FileListSearch from "./view";
|
||||||
} from 'actions/app'
|
|
||||||
import FileListSearch from './view'
|
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = state => ({
|
||||||
isSearching: selectIsSearching(state),
|
isSearching: selectIsSearching(state),
|
||||||
query: selectSearchQuery(state),
|
query: selectSearchQuery(state),
|
||||||
results: selectCurrentSearchResults(state)
|
results: selectCurrentSearchResults(state),
|
||||||
})
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
navigate: (path) => dispatch(doNavigate(path)),
|
navigate: path => dispatch(doNavigate(path)),
|
||||||
search: (search) => dispatch(doSearch(search))
|
search: search => dispatch(doSearch(search)),
|
||||||
})
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(FileListSearch)
|
export default connect(select, perform)(FileListSearch);
|
||||||
|
|
|
@ -1,76 +1,76 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import lbry from 'lbry';
|
import lbry from "lbry";
|
||||||
import lbryio from 'lbryio';
|
import lbryio from "lbryio";
|
||||||
import lbryuri from 'lbryuri';
|
import lbryuri from "lbryuri";
|
||||||
import lighthouse from 'lighthouse';
|
import lighthouse from "lighthouse";
|
||||||
import FileTile from 'component/fileTile'
|
import FileTile from "component/fileTile";
|
||||||
import Link from 'component/link'
|
import Link from "component/link";
|
||||||
import {ToolTip} from 'component/tooltip.js';
|
import { ToolTip } from "component/tooltip.js";
|
||||||
import {BusyMessage} from 'component/common.js';
|
import { BusyMessage } from "component/common.js";
|
||||||
|
|
||||||
const SearchNoResults = (props) => {
|
const SearchNoResults = props => {
|
||||||
const {
|
const { navigate, query } = props;
|
||||||
navigate,
|
|
||||||
query,
|
|
||||||
} = props
|
|
||||||
|
|
||||||
return <section>
|
return (
|
||||||
<span className="empty">
|
<section>
|
||||||
No one has checked anything in for {query} yet. { ' ' }
|
<span className="empty">
|
||||||
<Link label="Be the first" onClick={() => navigate('/publish')} />
|
{(__("No one has checked anything in for %s yet."), query)} {" "}
|
||||||
</span>
|
<Link label={__("Be the first")} onClick={() => navigate("/publish")} />
|
||||||
</section>;
|
</span>
|
||||||
}
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const FileListSearchResults = (props) => {
|
const FileListSearchResults = props => {
|
||||||
const {
|
const { results } = props;
|
||||||
results,
|
|
||||||
} = props
|
|
||||||
|
|
||||||
const rows = [],
|
const rows = [],
|
||||||
seenNames = {}; //fix this when the search API returns claim IDs
|
seenNames = {}; //fix this when the search API returns claim IDs
|
||||||
|
|
||||||
for (let {name, claim, claim_id, channel_name, channel_id, txid, nout} of results) {
|
for (let {
|
||||||
|
name,
|
||||||
|
claim,
|
||||||
|
claim_id,
|
||||||
|
channel_name,
|
||||||
|
channel_id,
|
||||||
|
txid,
|
||||||
|
nout,
|
||||||
|
} of results) {
|
||||||
const uri = lbryuri.build({
|
const uri = lbryuri.build({
|
||||||
channelName: channel_name,
|
channelName: channel_name,
|
||||||
contentName: name,
|
contentName: name,
|
||||||
claimId: channel_id || claim_id,
|
claimId: channel_id || claim_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
rows.push(
|
rows.push(<FileTile key={uri} uri={uri} />);
|
||||||
<FileTile key={uri} uri={uri} />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return (
|
return <div>{rows}</div>;
|
||||||
<div>{rows}</div>
|
};
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class FileListSearch extends React.Component{
|
class FileListSearch extends React.PureComponent {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.props.search(this.props.query)
|
this.props.search(this.props.query);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { isSearching, results } = this.props;
|
||||||
isSearching,
|
|
||||||
results
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{isSearching && !results &&
|
{isSearching &&
|
||||||
<BusyMessage message="Looking up the Dewey Decimals" />}
|
!results &&
|
||||||
|
<BusyMessage message={__("Looking up the Dewey Decimals")} />}
|
||||||
|
|
||||||
{isSearching && results &&
|
{isSearching &&
|
||||||
<BusyMessage message="Refreshing the Dewey Decimals" />}
|
results &&
|
||||||
|
<BusyMessage message={__("Refreshing the Dewey Decimals")} />}
|
||||||
|
|
||||||
{(results && !!results.length) ?
|
{results && !!results.length
|
||||||
<FileListSearchResults {...this.props} /> :
|
? <FileListSearchResults {...this.props} />
|
||||||
<SearchNoResults {...this.props} />}
|
: <SearchNoResults {...this.props} />}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FileListSearch
|
export default FileListSearch;
|
||||||
|
|
|
@ -1,31 +1,32 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect,
|
import { doFetchCostInfoForUri } from "actions/cost_info";
|
||||||
} from 'react-redux'
|
|
||||||
import {
|
|
||||||
doFetchCostInfoForUri,
|
|
||||||
} from 'actions/cost_info'
|
|
||||||
import {
|
import {
|
||||||
makeSelectCostInfoForUri,
|
makeSelectCostInfoForUri,
|
||||||
makeSelectFetchingCostInfoForUri,
|
makeSelectFetchingCostInfoForUri,
|
||||||
} from 'selectors/cost_info'
|
} from 'selectors/cost_info'
|
||||||
|
import {
|
||||||
|
makeSelectClaimForUri,
|
||||||
|
} from 'selectors/claims'
|
||||||
import FilePrice from './view'
|
import FilePrice from './view'
|
||||||
|
|
||||||
const makeSelect = () => {
|
const makeSelect = () => {
|
||||||
const selectCostInfoForUri = makeSelectCostInfoForUri()
|
const selectCostInfoForUri = makeSelectCostInfoForUri();
|
||||||
const selectFetchingCostInfoForUri = makeSelectFetchingCostInfoForUri()
|
const selectFetchingCostInfoForUri = makeSelectFetchingCostInfoForUri();
|
||||||
|
const selectClaim = makeSelectClaimForUri();
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
costInfo: selectCostInfoForUri(state, props),
|
costInfo: selectCostInfoForUri(state, props),
|
||||||
fetching: selectFetchingCostInfoForUri(state, props),
|
fetching: selectFetchingCostInfoForUri(state, props),
|
||||||
})
|
claim: selectClaim(state, props),
|
||||||
|
});
|
||||||
|
|
||||||
return select
|
return select;
|
||||||
}
|
};
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
fetchCostInfo: (uri) => dispatch(doFetchCostInfoForUri(uri)),
|
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
||||||
// cancelFetchCostInfo: (uri) => dispatch(doCancelFetchCostInfoForUri(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 React from "react";
|
||||||
import {
|
import { CreditAmount } from "component/common";
|
||||||
CreditAmount,
|
|
||||||
} from 'component/common'
|
|
||||||
|
|
||||||
class FilePrice extends React.Component{
|
class FilePrice extends React.PureComponent {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.fetchCost(this.props)
|
this.fetchCost(this.props);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
this.fetchCost(nextProps)
|
this.fetchCost(nextProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchCost(props) {
|
fetchCost(props) {
|
||||||
const {
|
const { costInfo, fetchCostInfo, uri, fetching, claim } = props;
|
||||||
costInfo,
|
|
||||||
fetchCostInfo,
|
|
||||||
uri,
|
|
||||||
fetching,
|
|
||||||
} = props
|
|
||||||
|
|
||||||
if (costInfo === undefined && !fetching) {
|
if (costInfo === undefined && !fetching && claim) {
|
||||||
fetchCostInfo(uri)
|
fetchCostInfo(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { costInfo, look = "indicator" } = this.props;
|
||||||
costInfo,
|
|
||||||
look = 'indicator',
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
const isEstimate = costInfo ? !costInfo.includesData : null
|
const isEstimate = costInfo ? !costInfo.includesData : null;
|
||||||
|
|
||||||
if (!costInfo) {
|
if (!costInfo) {
|
||||||
return <span className={`credit-amount credit-amount--${look}`}>???</span>;
|
return (
|
||||||
|
<span className={`credit-amount credit-amount--${look}`}>???</span>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <CreditAmount label={false} amount={costInfo.cost} isEstimate={isEstimate} showFree={true} />
|
return (
|
||||||
|
<CreditAmount
|
||||||
|
label={false}
|
||||||
|
amount={costInfo.cost}
|
||||||
|
isEstimate={isEstimate}
|
||||||
|
showFree={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FilePrice
|
export default FilePrice;
|
||||||
|
|
|
@ -1,33 +1,21 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect
|
import { doNavigate } from "actions/app";
|
||||||
} from 'react-redux'
|
import { doResolveUri } from "actions/content";
|
||||||
import {
|
|
||||||
doNavigate,
|
|
||||||
} from 'actions/app'
|
|
||||||
import {
|
|
||||||
doResolveUri,
|
|
||||||
} from 'actions/content'
|
|
||||||
import {
|
import {
|
||||||
makeSelectClaimForUri,
|
makeSelectClaimForUri,
|
||||||
makeSelectMetadataForUri,
|
makeSelectMetadataForUri,
|
||||||
} from 'selectors/claims'
|
} from "selectors/claims";
|
||||||
import {
|
import { makeSelectFileInfoForUri } from "selectors/file_info";
|
||||||
makeSelectFileInfoForUri,
|
import { selectObscureNsfw } from "selectors/app";
|
||||||
} from 'selectors/file_info'
|
import { makeSelectIsResolvingForUri } from "selectors/content";
|
||||||
import {
|
import FileTile from "./view";
|
||||||
selectObscureNsfw,
|
|
||||||
} from 'selectors/app'
|
|
||||||
import {
|
|
||||||
makeSelectIsResolvingForUri,
|
|
||||||
} from 'selectors/content'
|
|
||||||
import FileTile from './view'
|
|
||||||
|
|
||||||
const makeSelect = () => {
|
const makeSelect = () => {
|
||||||
const selectClaimForUri = makeSelectClaimForUri()
|
const selectClaimForUri = makeSelectClaimForUri();
|
||||||
const selectFileInfoForUri = makeSelectFileInfoForUri()
|
const selectFileInfoForUri = makeSelectFileInfoForUri();
|
||||||
const selectMetadataForUri = makeSelectMetadataForUri()
|
const selectMetadataForUri = makeSelectMetadataForUri();
|
||||||
const selectResolvingUri = makeSelectIsResolvingForUri()
|
const selectResolvingUri = makeSelectIsResolvingForUri();
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
claim: selectClaimForUri(state, props),
|
claim: selectClaimForUri(state, props),
|
||||||
|
@ -35,14 +23,14 @@ const makeSelect = () => {
|
||||||
obscureNsfw: selectObscureNsfw(state),
|
obscureNsfw: selectObscureNsfw(state),
|
||||||
metadata: selectMetadataForUri(state, props),
|
metadata: selectMetadataForUri(state, props),
|
||||||
isResolvingUri: selectResolvingUri(state, props),
|
isResolvingUri: selectResolvingUri(state, props),
|
||||||
})
|
});
|
||||||
|
|
||||||
return select
|
return select;
|
||||||
}
|
};
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||||
resolveUri: (uri) => dispatch(doResolveUri(uri)),
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
})
|
});
|
||||||
|
|
||||||
export default connect(makeSelect, perform)(FileTile)
|
export default connect(makeSelect, perform)(FileTile);
|
||||||
|
|
|
@ -1,38 +1,41 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import lbry from 'lbry.js';
|
import lbry from "lbry.js";
|
||||||
import lbryuri from 'lbryuri.js';
|
import lbryuri from "lbryuri.js";
|
||||||
import Link from 'component/link';
|
import Link from "component/link";
|
||||||
import FileActions from 'component/fileActions';
|
import FileActions from "component/fileActions";
|
||||||
import {Thumbnail, TruncatedText,} from 'component/common.js';
|
import { Thumbnail, TruncatedText } from "component/common.js";
|
||||||
import FilePrice from 'component/filePrice'
|
import FilePrice from "component/filePrice";
|
||||||
import UriIndicator from 'component/uriIndicator';
|
import UriIndicator from "component/uriIndicator";
|
||||||
|
|
||||||
class FileTile extends React.Component {
|
class FileTile extends React.PureComponent {
|
||||||
static SHOW_EMPTY_PUBLISH = "publish"
|
static SHOW_EMPTY_PUBLISH = "publish";
|
||||||
static SHOW_EMPTY_PENDING = "pending"
|
static SHOW_EMPTY_PENDING = "pending";
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
showNsfwHelp: false,
|
showNsfwHelp: false,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {
|
this.resolve(this.props);
|
||||||
isResolvingUri,
|
}
|
||||||
resolveUri,
|
|
||||||
claim,
|
|
||||||
uri,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
if(!isResolvingUri && !claim && uri) {
|
componentWillReceiveProps(nextProps) {
|
||||||
resolveUri(uri)
|
this.resolve(nextProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolve({ isResolvingUri, claim, uri, resolveUri }) {
|
||||||
|
if (!isResolvingUri && claim === undefined && uri) resolveUri(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseOver() {
|
handleMouseOver() {
|
||||||
if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) {
|
if (
|
||||||
|
this.props.obscureNsfw &&
|
||||||
|
this.props.metadata &&
|
||||||
|
this.props.metadata.nsfw
|
||||||
|
) {
|
||||||
this.setState({
|
this.setState({
|
||||||
showNsfwHelp: true,
|
showNsfwHelp: true,
|
||||||
});
|
});
|
||||||
|
@ -55,40 +58,61 @@ class FileTile extends React.Component {
|
||||||
showEmpty,
|
showEmpty,
|
||||||
navigate,
|
navigate,
|
||||||
hidePrice,
|
hidePrice,
|
||||||
} = this.props
|
} = this.props;
|
||||||
|
|
||||||
const uri = lbryuri.normalize(this.props.uri);
|
const uri = lbryuri.normalize(this.props.uri);
|
||||||
const isClaimed = !!claim;
|
const isClaimed = !!claim;
|
||||||
const isClaimable = lbryuri.isClaimable(uri)
|
const isClaimable = lbryuri.isClaimable(uri);
|
||||||
const title = isClaimed && metadata && metadata.title ? metadata.title : uri;
|
const title = isClaimed && metadata && metadata.title
|
||||||
|
? metadata.title
|
||||||
|
: uri;
|
||||||
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
||||||
let onClick = () => navigate('/show', { uri })
|
let onClick = () => navigate("/show", { uri });
|
||||||
|
|
||||||
let description = ""
|
let description = "";
|
||||||
if (isClaimed) {
|
if (isClaimed) {
|
||||||
description = metadata && metadata.description
|
description = metadata && metadata.description;
|
||||||
} else if (isResolvingUri) {
|
} else if (isResolvingUri) {
|
||||||
description = "Loading..."
|
description = __("Loading...");
|
||||||
} else if (showEmpty === FileTile.SHOW_EMPTY_PUBLISH) {
|
} else if (showEmpty === FileTile.SHOW_EMPTY_PUBLISH) {
|
||||||
onClick = () => navigate('/publish', { })
|
onClick = () => navigate("/publish", {});
|
||||||
description = <span className="empty">
|
description = (
|
||||||
This location is unused. { ' ' }
|
<span className="empty">
|
||||||
{ isClaimable && <span className="button-text">Put something here!</span> }
|
{__("This location is unused.")} {" "}
|
||||||
</span>
|
{isClaimable &&
|
||||||
|
<span className="button-text">{__("Put something here!")}</span>}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
} else if (showEmpty === FileTile.SHOW_EMPTY_PENDING) {
|
} else if (showEmpty === FileTile.SHOW_EMPTY_PENDING) {
|
||||||
description = <span className="empty">This file is pending confirmation.</span>
|
description = (
|
||||||
|
<span className="empty">
|
||||||
|
{__("This file is pending confirmation.")}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={ 'file-tile card ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver.bind(this)} onMouseLeave={this.handleMouseOut.bind(this)}>
|
<section
|
||||||
|
className={"file-tile card " + (obscureNsfw ? "card--obscured " : "")}
|
||||||
|
onMouseEnter={this.handleMouseOver.bind(this)}
|
||||||
|
onMouseLeave={this.handleMouseOut.bind(this)}
|
||||||
|
>
|
||||||
<Link onClick={onClick} className="card__link">
|
<Link onClick={onClick} className="card__link">
|
||||||
<div className={"card__inner file-tile__row"}>
|
<div className={"card__inner file-tile__row"}>
|
||||||
<div className="card__media"
|
<div
|
||||||
style={{ backgroundImage: "url('" + (metadata && metadata.thumbnail ? metadata.thumbnail : lbry.imagePath('default-thumb.svg')) + "')" }}>
|
className="card__media"
|
||||||
</div>
|
style={{
|
||||||
|
backgroundImage:
|
||||||
|
"url('" +
|
||||||
|
(metadata && metadata.thumbnail
|
||||||
|
? metadata.thumbnail
|
||||||
|
: lbry.imagePath("default-thumb.svg")) +
|
||||||
|
"')",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<div className="file-tile__content">
|
<div className="file-tile__content">
|
||||||
<div className="card__title-primary">
|
<div className="card__title-primary">
|
||||||
{ !hidePrice ? <FilePrice uri={this.props.uri} /> : null}
|
{!hidePrice ? <FilePrice uri={this.props.uri} /> : null}
|
||||||
<div className="meta">{uri}</div>
|
<div className="meta">{uri}</div>
|
||||||
<h3><TruncatedText lines={1}>{title}</TruncatedText></h3>
|
<h3><TruncatedText lines={1}>{title}</TruncatedText></h3>
|
||||||
</div>
|
</div>
|
||||||
|
@ -101,16 +125,23 @@ class FileTile extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
{this.state.showNsfwHelp
|
{this.state.showNsfwHelp
|
||||||
? <div className='card-overlay'>
|
? <div className="card-overlay">
|
||||||
<p>
|
<p>
|
||||||
This content is Not Safe For Work.
|
{__(
|
||||||
To view adult content, please change your <Link className="button-text" onClick={() => navigate('/settings')} label="Settings" />.
|
"This content is Not Safe For Work. To view adult content, please change your"
|
||||||
</p>
|
)}
|
||||||
</div>
|
{" "}
|
||||||
|
<Link
|
||||||
|
className="button-text"
|
||||||
|
onClick={() => navigate("/settings")}
|
||||||
|
label={__("Settings")}
|
||||||
|
/>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
: null}
|
: null}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FileTile
|
export default FileTile;
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import FileSelector from './file-selector.js';
|
import FileSelector from "./file-selector.js";
|
||||||
import {Icon} from './common.js';
|
import { Icon } from "./common.js";
|
||||||
|
|
||||||
var formFieldCounter = 0,
|
var formFieldCounter = 0,
|
||||||
formFieldFileSelectorTypes = ['file', 'directory'],
|
formFieldFileSelectorTypes = ["file", "directory"],
|
||||||
formFieldNestedLabelTypes = ['radio', 'checkbox'];
|
formFieldNestedLabelTypes = ["radio", "checkbox"];
|
||||||
|
|
||||||
function formFieldId() {
|
function formFieldId() {
|
||||||
return "form-field-" + (++formFieldCounter);
|
return "form-field-" + ++formFieldCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FormField extends React.Component {
|
export class FormField extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
type: React.PropTypes.string.isRequired,
|
type: React.PropTypes.string.isRequired,
|
||||||
prefix: React.PropTypes.string,
|
prefix: React.PropTypes.string,
|
||||||
postfix: React.PropTypes.string,
|
postfix: React.PropTypes.string,
|
||||||
hasError: React.PropTypes.bool
|
hasError: React.PropTypes.bool,
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this._fieldRequiredText = 'This field is required';
|
this._fieldRequiredText = __("This field is required");
|
||||||
this._type = null;
|
this._type = null;
|
||||||
this._element = null;
|
this._element = null;
|
||||||
|
|
||||||
|
@ -32,15 +32,15 @@ export class FormField extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
if (['text', 'number', 'radio', 'checkbox'].includes(this.props.type)) {
|
if (["text", "number", "radio", "checkbox"].includes(this.props.type)) {
|
||||||
this._element = 'input';
|
this._element = "input";
|
||||||
this._type = this.props.type;
|
this._type = this.props.type;
|
||||||
} else if (this.props.type == 'text-number') {
|
} else if (this.props.type == "text-number") {
|
||||||
this._element = 'input';
|
this._element = "input";
|
||||||
this._type = 'text';
|
this._type = "text";
|
||||||
} else if (formFieldFileSelectorTypes.includes(this.props.type)) {
|
} else if (formFieldFileSelectorTypes.includes(this.props.type)) {
|
||||||
this._element = 'input';
|
this._element = "input";
|
||||||
this._type = 'hidden';
|
this._type = "hidden";
|
||||||
} else {
|
} else {
|
||||||
// Non <input> field, e.g. <select>, <textarea>
|
// Non <input> field, e.g. <select>, <textarea>
|
||||||
this._element = this.props.type;
|
this._element = this.props.type;
|
||||||
|
@ -52,15 +52,16 @@ export class FormField extends React.Component {
|
||||||
* We have to add the webkitdirectory attribute here because React doesn't allow it in JSX
|
* We have to add the webkitdirectory attribute here because React doesn't allow it in JSX
|
||||||
* https://github.com/facebook/react/issues/3468
|
* https://github.com/facebook/react/issues/3468
|
||||||
*/
|
*/
|
||||||
if (this.props.type == 'directory') {
|
if (this.props.type == "directory") {
|
||||||
this.refs.field.webkitdirectory = true;
|
this.refs.field.webkitdirectory = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFileChosen(path) {
|
handleFileChosen(path) {
|
||||||
this.refs.field.value = 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
|
if (this.props.onChange) {
|
||||||
const event = new Event('change', {bubbles: true})
|
// 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.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);
|
this.props.onChange(event);
|
||||||
}
|
}
|
||||||
|
@ -78,7 +79,7 @@ export class FormField extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
getValue() {
|
getValue() {
|
||||||
if (this.props.type == 'checkbox') {
|
if (this.props.type == "checkbox") {
|
||||||
return this.refs.field.checked;
|
return this.refs.field.checked;
|
||||||
} else {
|
} else {
|
||||||
return this.refs.field.value;
|
return this.refs.field.value;
|
||||||
|
@ -92,9 +93,12 @@ export class FormField extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
// Pass all unhandled props to the field element
|
// Pass all unhandled props to the field element
|
||||||
const otherProps = Object.assign({}, this.props),
|
const otherProps = Object.assign({}, this.props),
|
||||||
isError = this.state.isError !== null ? this.state.isError : this.props.hasError,
|
isError = this.state.isError !== null
|
||||||
elementId = this.props.id ? this.props.id : formFieldId(),
|
? this.state.isError
|
||||||
renderElementInsideLabel = this.props.label && formFieldNestedLabelTypes.includes(this.props.type);
|
: 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.type;
|
||||||
delete otherProps.label;
|
delete otherProps.label;
|
||||||
|
@ -103,44 +107,90 @@ export class FormField extends React.Component {
|
||||||
delete otherProps.postfix;
|
delete otherProps.postfix;
|
||||||
delete otherProps.prefix;
|
delete otherProps.prefix;
|
||||||
|
|
||||||
const element = <this._element id={elementId} type={this._type} name={this.props.name} ref="field" placeholder={this.props.placeholder}
|
const element = (
|
||||||
className={'form-field__input form-field__input-' + this.props.type + ' ' + (this.props.className || '') + (isError ? 'form-field__input--error' : '')}
|
<this._element
|
||||||
{...otherProps}>
|
id={elementId}
|
||||||
{this.props.children}
|
type={this._type}
|
||||||
</this._element>;
|
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}>
|
return (
|
||||||
{ this.props.prefix ? <span className="form-field__prefix">{this.props.prefix}</span> : '' }
|
<div className={"form-field form-field--" + this.props.type}>
|
||||||
{ renderElementInsideLabel ?
|
{this.props.prefix
|
||||||
<label htmlFor={elementId} className={"form-field__label " + (isError ? 'form-field__label--error' : '')}>
|
? <span className="form-field__prefix">{this.props.prefix}</span>
|
||||||
{element}
|
: ""}
|
||||||
{this.props.label}
|
{renderElementInsideLabel
|
||||||
</label> :
|
? <label
|
||||||
element }
|
htmlFor={elementId}
|
||||||
{ formFieldFileSelectorTypes.includes(this.props.type) ?
|
className={
|
||||||
<FileSelector type={this.props.type} onFileChosen={this.handleFileChosen.bind(this)}
|
"form-field__label " +
|
||||||
{... this.props.defaultValue ? {initPath: this.props.defaultValue} : {}} /> :
|
(isError ? "form-field__label--error" : "")
|
||||||
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> : '' }
|
{element}
|
||||||
</div>
|
{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 {
|
export class FormRow extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
label: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.element]),
|
label: React.PropTypes.oneOfType([
|
||||||
|
React.PropTypes.string,
|
||||||
|
React.PropTypes.element,
|
||||||
|
]),
|
||||||
// helper: React.PropTypes.html,
|
// helper: React.PropTypes.html,
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this._fieldRequiredText = 'This field is required';
|
this._fieldRequiredText = __("This field is required");
|
||||||
|
|
||||||
this.state = {
|
this.state = this.getStateFromProps(props);
|
||||||
isError: false,
|
}
|
||||||
errorMessage: null,
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
this.setState(this.getStateFromProps(nextProps));
|
||||||
|
}
|
||||||
|
|
||||||
|
getStateFromProps(props) {
|
||||||
|
return {
|
||||||
|
isError: !!props.errorMessage,
|
||||||
|
errorMessage: typeof props.errorMessage === "string"
|
||||||
|
? props.errorMessage
|
||||||
|
: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +208,7 @@ export class FormRow extends React.Component {
|
||||||
clearError(text) {
|
clearError(text) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isError: false,
|
isError: false,
|
||||||
errorMessage: ''
|
errorMessage: "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,24 +226,45 @@ export class FormRow extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const fieldProps = Object.assign({}, this.props),
|
const fieldProps = Object.assign({}, this.props),
|
||||||
elementId = formFieldId(),
|
elementId = formFieldId(),
|
||||||
renderLabelInFormField = formFieldNestedLabelTypes.includes(this.props.type);
|
renderLabelInFormField = formFieldNestedLabelTypes.includes(
|
||||||
|
this.props.type
|
||||||
|
);
|
||||||
|
|
||||||
if (!renderLabelInFormField) {
|
if (!renderLabelInFormField) {
|
||||||
delete fieldProps.label;
|
delete fieldProps.label;
|
||||||
}
|
}
|
||||||
delete fieldProps.helper;
|
delete fieldProps.helper;
|
||||||
|
delete fieldProps.errorMessage;
|
||||||
|
|
||||||
return <div className="form-row">
|
return (
|
||||||
{ this.props.label && !renderLabelInFormField ?
|
<div className="form-row">
|
||||||
<div className={"form-row__label-row " + (this.props.labelPrefix ? "form-row__label-row--prefix" : "") }>
|
{this.props.label && !renderLabelInFormField
|
||||||
<label htmlFor={elementId} className={"form-field__label " + (this.state.isError ? 'form-field__label--error' : '')}>
|
? <div
|
||||||
{this.props.label}
|
className={
|
||||||
</label>
|
"form-row__label-row " +
|
||||||
</div> : '' }
|
(this.props.labelPrefix ? "form-row__label-row--prefix" : "")
|
||||||
<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> : '' }
|
<label
|
||||||
</div>
|
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,24 +1,18 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import lbry from 'lbry'
|
import lbry from "lbry";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect
|
import { selectBalance } from "selectors/wallet";
|
||||||
} from 'react-redux'
|
import { doNavigate, doHistoryBack } from "actions/app";
|
||||||
import {
|
import Header from "./view";
|
||||||
selectBalance
|
|
||||||
} from 'selectors/wallet'
|
|
||||||
import {
|
|
||||||
doNavigate,
|
|
||||||
doHistoryBack,
|
|
||||||
} from 'actions/app'
|
|
||||||
import Header from './view'
|
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = state => ({
|
||||||
balance: lbry.formatCredits(selectBalance(state), 1)
|
balance: lbry.formatCredits(selectBalance(state), 1),
|
||||||
})
|
publish: __("Publish"),
|
||||||
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
navigate: (path) => dispatch(doNavigate(path)),
|
navigate: path => dispatch(doNavigate(path)),
|
||||||
back: () => dispatch(doHistoryBack()),
|
back: () => dispatch(doHistoryBack()),
|
||||||
})
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(Header)
|
export default connect(select, perform)(Header);
|
||||||
|
|
|
@ -1,37 +1,57 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import Link from 'component/link';
|
import Link from "component/link";
|
||||||
import WunderBar from 'component/wunderbar';
|
import WunderBar from "component/wunderbar";
|
||||||
|
|
||||||
export const Header = (props) => {
|
export const Header = props => {
|
||||||
const {
|
const { balance, back, navigate, publish } = props;
|
||||||
balance,
|
|
||||||
back,
|
|
||||||
navigate
|
|
||||||
} = props
|
|
||||||
|
|
||||||
return <header id="header">
|
return (
|
||||||
<div className="header__item">
|
<header id="header">
|
||||||
<Link onClick={back} button="alt button--flat" icon="icon-arrow-left" />
|
<div className="header__item">
|
||||||
</div>
|
<Link onClick={back} button="alt button--flat" icon="icon-arrow-left" />
|
||||||
<div className="header__item">
|
</div>
|
||||||
<Link onClick={() => navigate('/discover')} button="alt button--flat" icon="icon-home" />
|
<div className="header__item">
|
||||||
</div>
|
<Link
|
||||||
<div className="header__item header__item--wunderbar">
|
onClick={() => navigate("/discover")}
|
||||||
<WunderBar/>
|
button="alt button--flat"
|
||||||
</div>
|
icon="icon-home"
|
||||||
<div className="header__item">
|
/>
|
||||||
<Link onClick={() => navigate('/wallet')} button="text" icon="icon-bank" label={balance} ></Link>
|
</div>
|
||||||
</div>
|
<div className="header__item header__item--wunderbar">
|
||||||
<div className="header__item">
|
<WunderBar />
|
||||||
<Link onClick={() => navigate('/publish')} button="primary button--flat" icon="icon-upload" label="Publish" />
|
</div>
|
||||||
</div>
|
<div className="header__item">
|
||||||
<div className="header__item">
|
<Link
|
||||||
<Link onClick={() => navigate('/downloaded')} button="alt button--flat" icon="icon-folder" />
|
onClick={() => navigate("/wallet")}
|
||||||
</div>
|
button="text"
|
||||||
<div className="header__item">
|
icon="icon-bank"
|
||||||
<Link onClick={() => navigate('/settings')} button="alt button--flat" icon="icon-gear" />
|
label={balance}
|
||||||
</div>
|
/>
|
||||||
</header>
|
</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;
|
export default Header;
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect,
|
import Link from "./view";
|
||||||
} from 'react-redux'
|
|
||||||
import Link from './view'
|
|
||||||
|
|
||||||
export default connect(null, null)(Link)
|
export default connect(null, null)(Link);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import {Icon} from 'component/common.js';
|
import { Icon } from "component/common.js";
|
||||||
|
|
||||||
const Link = (props) => {
|
const Link = props => {
|
||||||
const {
|
const {
|
||||||
href,
|
href,
|
||||||
title,
|
title,
|
||||||
|
@ -14,34 +14,40 @@ const Link = (props) => {
|
||||||
hidden,
|
hidden,
|
||||||
disabled,
|
disabled,
|
||||||
children,
|
children,
|
||||||
} = props
|
} = 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;
|
let content;
|
||||||
if (children) {
|
if (children) {
|
||||||
content = children
|
content = children;
|
||||||
} else {
|
} else {
|
||||||
content = (
|
content = (
|
||||||
<span {... 'button' in props ? {className: 'button__content'} : {}}>
|
<span {...("button" in props ? { className: "button__content" } : {})}>
|
||||||
{'icon' in props ? <Icon icon={icon} fixed={true} /> : null}
|
{"icon" in props ? <Icon icon={icon} fixed={true} /> : null}
|
||||||
{label ? <span className="link-label">{label}</span> : null}
|
{label ? <span className="link-label">{label}</span> : null}
|
||||||
{'badge' in props ? <span className="badge">{badge}</span> : null}
|
{"badge" in props ? <span className="badge">{badge}</span> : null}
|
||||||
</span>
|
</span>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a className={className} href={href || 'javascript:;'} title={title}
|
<a
|
||||||
|
className={className}
|
||||||
|
href={href || "javascript:;"}
|
||||||
|
title={title}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
{... 'style' in props ? {style: style} : {}}>
|
{...("style" in props ? { style: style } : {})}
|
||||||
|
>
|
||||||
{content}
|
{content}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Link
|
export default Link;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import lbry from '../lbry.js';
|
import lbry from "../lbry.js";
|
||||||
import {BusyMessage, Icon} from './common.js';
|
import { BusyMessage, Icon } from "./common.js";
|
||||||
import Link from 'component/link'
|
import Link from "component/link";
|
||||||
|
|
||||||
class LoadScreen extends React.Component {
|
class LoadScreen extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
message: React.PropTypes.string.isRequired,
|
message: React.PropTypes.string.isRequired,
|
||||||
details: React.PropTypes.string,
|
details: React.PropTypes.string,
|
||||||
isWarning: React.PropTypes.bool,
|
isWarning: React.PropTypes.bool,
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -22,25 +22,33 @@ class LoadScreen extends React.Component {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
isWarning: false,
|
isWarning: false,
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const imgSrc = lbry.imagePath('lbry-white-485x160.png');
|
const imgSrc = lbry.imagePath("lbry-white-485x160.png");
|
||||||
return (
|
return (
|
||||||
<div className="load-screen">
|
<div className="load-screen">
|
||||||
<img src={imgSrc} alt="LBRY"/>
|
<img src={imgSrc} alt="LBRY" />
|
||||||
<div className="load-screen__message">
|
<div className="load-screen__message">
|
||||||
<h3>
|
<h3>
|
||||||
{!this.props.isWarning ?
|
{!this.props.isWarning
|
||||||
<BusyMessage message={this.props.message} /> :
|
? <BusyMessage message={this.props.message} />
|
||||||
<span><Icon icon="icon-warning" />{' ' + this.props.message}</span> }
|
: <span>
|
||||||
|
<Icon icon="icon-warning" />{" " + this.props.message}
|
||||||
|
</span>}
|
||||||
</h3>
|
</h3>
|
||||||
<span className={'load-screen__details ' + (this.props.isWarning ? 'load-screen__details--warning' : '')}>{this.props.details}</span>
|
<span
|
||||||
|
className={
|
||||||
|
"load-screen__details " +
|
||||||
|
(this.props.isWarning ? "load-screen__details--warning" : "")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{this.props.details}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default LoadScreen;
|
export default LoadScreen;
|
||||||
|
|
|
@ -1,34 +1,38 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import {Icon} from './common.js';
|
import { Icon } from "./common.js";
|
||||||
import Link from 'component/link';
|
import Link from "component/link";
|
||||||
|
|
||||||
export class DropDownMenuItem extends React.Component {
|
export class DropDownMenuItem extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
href: React.PropTypes.string,
|
href: React.PropTypes.string,
|
||||||
label: React.PropTypes.string,
|
label: React.PropTypes.string,
|
||||||
icon: React.PropTypes.string,
|
icon: React.PropTypes.string,
|
||||||
onClick: React.PropTypes.func,
|
onClick: React.PropTypes.func,
|
||||||
}
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
iconPosition: 'left',
|
iconPosition: "left",
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var icon = (this.props.icon ? <Icon icon={this.props.icon} fixed /> : null);
|
var icon = this.props.icon ? <Icon icon={this.props.icon} fixed /> : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a className="menu__menu-item" onClick={this.props.onClick}
|
<a
|
||||||
href={this.props.href || 'javascript:'} label={this.props.label}>
|
className="menu__menu-item"
|
||||||
{this.props.iconPosition == 'left' ? icon : null}
|
onClick={this.props.onClick}
|
||||||
|
href={this.props.href || "javascript:"}
|
||||||
|
label={this.props.label}
|
||||||
|
>
|
||||||
|
{this.props.iconPosition == "left" ? icon : null}
|
||||||
{this.props.label}
|
{this.props.label}
|
||||||
{this.props.iconPosition == 'left' ? null : icon}
|
{this.props.iconPosition == "left" ? null : icon}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DropDownMenu extends React.Component {
|
export class DropDownMenu extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
@ -42,7 +46,7 @@ export class DropDownMenu extends React.Component {
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (this._isWindowClickBound) {
|
if (this._isWindowClickBound) {
|
||||||
window.removeEventListener('click', this.handleWindowClick, false);
|
window.removeEventListener("click", this.handleWindowClick, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +56,7 @@ export class DropDownMenu extends React.Component {
|
||||||
});
|
});
|
||||||
if (!this.state.menuOpen && !this._isWindowClickBound) {
|
if (!this.state.menuOpen && !this._isWindowClickBound) {
|
||||||
this._isWindowClickBound = true;
|
this._isWindowClickBound = true;
|
||||||
window.addEventListener('click', this.handleWindowClick, false);
|
window.addEventListener("click", this.handleWindowClick, false);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -66,10 +70,12 @@ export class DropDownMenu extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleWindowClick(e) {
|
handleWindowClick(e) {
|
||||||
if (this.state.menuOpen &&
|
if (
|
||||||
(!this._menuDiv || !this._menuDiv.contains(e.target))) {
|
this.state.menuOpen &&
|
||||||
|
(!this._menuDiv || !this._menuDiv.contains(e.target))
|
||||||
|
) {
|
||||||
this.setState({
|
this.setState({
|
||||||
menuOpen: false
|
menuOpen: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,13 +83,26 @@ export class DropDownMenu extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
if (!this.state.menuOpen && this._isWindowClickBound) {
|
if (!this.state.menuOpen && this._isWindowClickBound) {
|
||||||
this._isWindowClickBound = false;
|
this._isWindowClickBound = false;
|
||||||
window.removeEventListener('click', this.handleWindowClick, false);
|
window.removeEventListener("click", this.handleWindowClick, false);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="menu-container">
|
<div className="menu-container">
|
||||||
<Link ref={(span) => this._menuButton = span} button="text" icon="icon-ellipsis-v" onClick={(event) => { this.handleMenuIconClick(event) }} />
|
<Link
|
||||||
|
ref={span => (this._menuButton = span)}
|
||||||
|
button="text"
|
||||||
|
icon="icon-ellipsis-v"
|
||||||
|
onClick={event => {
|
||||||
|
this.handleMenuIconClick(event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{this.state.menuOpen
|
{this.state.menuOpen
|
||||||
? <div ref={(div) => this._menuDiv = div} className="menu" onClick={(event) => { this.handleMenuClick(event) }}>
|
? <div
|
||||||
|
ref={div => (this._menuDiv = div)}
|
||||||
|
className="menu"
|
||||||
|
onClick={event => {
|
||||||
|
this.handleMenuClick(event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
: null}
|
: null}
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import ReactModal from 'react-modal';
|
import ReactModal from "react-modal";
|
||||||
|
|
||||||
export class ModalPage extends React.Component {
|
export class ModalPage extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<ReactModal onCloseRequested={this.props.onAborted || this.props.onConfirmed} {...this.props}
|
<ReactModal
|
||||||
className={(this.props.className || '') + ' modal-page'}
|
onCloseRequested={this.props.onAborted || this.props.onConfirmed}
|
||||||
overlayClassName="modal-overlay">
|
{...this.props}
|
||||||
|
className={(this.props.className || "") + " modal-page"}
|
||||||
|
overlayClassName="modal-overlay"
|
||||||
|
>
|
||||||
<div className="modal-page__content">
|
<div className="modal-page__content">
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,4 +18,4 @@ export class ModalPage extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ModalPage
|
export default ModalPage;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import ReactModal from 'react-modal';
|
import ReactModal from "react-modal";
|
||||||
import Link from 'component/link';
|
import Link from "component/link";
|
||||||
|
import app from "../app.js";
|
||||||
|
|
||||||
|
export class Modal extends React.PureComponent {
|
||||||
export class Modal extends React.Component {
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
type: React.PropTypes.oneOf(['alert', 'confirm', 'custom']),
|
type: React.PropTypes.oneOf(["alert", "confirm", "custom"]),
|
||||||
overlay: React.PropTypes.bool,
|
overlay: React.PropTypes.bool,
|
||||||
onConfirmed: React.PropTypes.func,
|
onConfirmed: React.PropTypes.func,
|
||||||
onAborted: React.PropTypes.func,
|
onAborted: React.PropTypes.func,
|
||||||
|
@ -13,56 +13,75 @@ export class Modal extends React.Component {
|
||||||
abortButtonLabel: React.PropTypes.string,
|
abortButtonLabel: React.PropTypes.string,
|
||||||
confirmButtonDisabled: React.PropTypes.bool,
|
confirmButtonDisabled: React.PropTypes.bool,
|
||||||
abortButtonDisabled: React.PropTypes.bool,
|
abortButtonDisabled: React.PropTypes.bool,
|
||||||
}
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
type: 'alert',
|
type: "alert",
|
||||||
overlay: true,
|
overlay: true,
|
||||||
confirmButtonLabel: 'OK',
|
confirmButtonLabel: app.i18n.__("OK"),
|
||||||
abortButtonLabel: 'Cancel',
|
abortButtonLabel: app.i18n.__("Cancel"),
|
||||||
confirmButtonDisabled: false,
|
confirmButtonDisabled: false,
|
||||||
abortButtonDisabled: false,
|
abortButtonDisabled: false,
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<ReactModal onCloseRequested={this.props.onAborted || this.props.onConfirmed} {...this.props}
|
<ReactModal
|
||||||
className={(this.props.className || '') + ' modal'}
|
onCloseRequested={this.props.onAborted || this.props.onConfirmed}
|
||||||
overlayClassName={![null, undefined, ""].includes(this.props.overlayClassName) ? this.props.overlayClassName : 'modal-overlay'}>
|
{...this.props}
|
||||||
|
className={(this.props.className || "") + " modal"}
|
||||||
|
overlayClassName={
|
||||||
|
![null, undefined, ""].includes(this.props.overlayClassName)
|
||||||
|
? this.props.overlayClassName
|
||||||
|
: "modal-overlay"
|
||||||
|
}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
{this.props.type == 'custom' // custom modals define their own buttons
|
{this.props.type == "custom" // custom modals define their own buttons
|
||||||
? null
|
? null
|
||||||
: <div className="modal__buttons">
|
: <div className="modal__buttons">
|
||||||
<Link button="primary" label={this.props.confirmButtonLabel} className="modal__button" disabled={this.props.confirmButtonDisabled} onClick={this.props.onConfirmed} />
|
<Link
|
||||||
{this.props.type == 'confirm'
|
button="primary"
|
||||||
? <Link button="alt" label={this.props.abortButtonLabel} className="modal__button" disabled={this.props.abortButtonDisabled} onClick={this.props.onAborted} />
|
label={this.props.confirmButtonLabel}
|
||||||
: null}
|
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>}
|
</div>}
|
||||||
</ReactModal>
|
</ReactModal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExpandableModal extends React.Component {
|
export class ExpandableModal extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
expandButtonLabel: React.PropTypes.string,
|
expandButtonLabel: React.PropTypes.string,
|
||||||
extraContent: React.PropTypes.element,
|
extraContent: React.PropTypes.element,
|
||||||
}
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
confirmButtonLabel: 'OK',
|
confirmButtonLabel: app.i18n.__("OK"),
|
||||||
expandButtonLabel: 'Show More...',
|
expandButtonLabel: app.i18n.__("Show More..."),
|
||||||
hideButtonLabel: 'Show Less',
|
hideButtonLabel: app.i18n.__("Show Less"),
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
expanded: false,
|
expanded: false,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleExpanded() {
|
toggleExpanded() {
|
||||||
|
@ -73,15 +92,28 @@ export class ExpandableModal extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Modal type="custom" {... this.props}>
|
<Modal type="custom" {...this.props}>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
{this.state.expanded
|
{this.state.expanded ? this.props.extraContent : null}
|
||||||
? this.props.extraContent
|
|
||||||
: null}
|
|
||||||
<div className="modal__buttons">
|
<div className="modal__buttons">
|
||||||
<Link button="primary" label={this.props.confirmButtonLabel} className="modal__button" onClick={this.props.onConfirmed} />
|
<Link
|
||||||
<Link button="alt" label={!this.state.expanded ? this.props.expandButtonLabel : this.props.hideButtonLabel}
|
button="primary"
|
||||||
className="modal__button" onClick={() => { this.toggleExpanded() }} />
|
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>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,17 +1,23 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
|
|
||||||
export class Notice extends React.Component {
|
export class Notice extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
isError: React.PropTypes.bool,
|
isError: React.PropTypes.bool,
|
||||||
}
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
isError: false,
|
isError: false,
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<section className={'notice ' + (this.props.isError ? 'notice--error ' : '') + (this.props.className || '')}>
|
<section
|
||||||
|
className={
|
||||||
|
"notice " +
|
||||||
|
(this.props.isError ? "notice--error " : "") +
|
||||||
|
(this.props.className || "")
|
||||||
|
}
|
||||||
|
>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import lbry from 'lbry'
|
|
||||||
import {Icon} from 'component/common';
|
|
||||||
import Modal from 'component/modal';
|
|
||||||
import rewards from 'rewards';
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
claimable: true,
|
|
||||||
pending: false,
|
|
||||||
errorMessage: null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
this.refreshClaimable();
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
35
ui/js/component/rewardLink/index.js
Normal file
35
ui/js/component/rewardLink/index.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import {
|
||||||
|
makeSelectHasClaimedReward,
|
||||||
|
makeSelectClaimRewardError,
|
||||||
|
makeSelectRewardByType,
|
||||||
|
makeSelectIsRewardClaimPending,
|
||||||
|
} from "selectors/rewards";
|
||||||
|
import { doNavigate } from "actions/app";
|
||||||
|
import { doClaimReward, doClaimRewardClearError } from "actions/rewards";
|
||||||
|
import RewardLink from "./view";
|
||||||
|
|
||||||
|
const makeSelect = () => {
|
||||||
|
const selectHasClaimedReward = makeSelectHasClaimedReward();
|
||||||
|
const selectIsPending = makeSelectIsRewardClaimPending();
|
||||||
|
const selectReward = makeSelectRewardByType();
|
||||||
|
const selectError = makeSelectClaimRewardError();
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
isClaimed: selectHasClaimedReward(state, props),
|
||||||
|
errorMessage: selectError(state, props),
|
||||||
|
isPending: selectIsPending(state, props),
|
||||||
|
reward: selectReward(state, props),
|
||||||
|
});
|
||||||
|
|
||||||
|
return select;
|
||||||
|
};
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
claimReward: reward => dispatch(doClaimReward(reward, true)),
|
||||||
|
clearError: reward => dispatch(doClaimRewardClearError(reward)),
|
||||||
|
navigate: path => dispatch(doNavigate(path)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(makeSelect, perform)(RewardLink);
|
44
ui/js/component/rewardLink/view.jsx
Normal file
44
ui/js/component/rewardLink/view.jsx
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Icon } from "component/common";
|
||||||
|
import Modal from "component/modal";
|
||||||
|
import Link from "component/link";
|
||||||
|
|
||||||
|
const RewardLink = props => {
|
||||||
|
const {
|
||||||
|
reward,
|
||||||
|
button,
|
||||||
|
claimReward,
|
||||||
|
clearError,
|
||||||
|
errorMessage,
|
||||||
|
isClaimed,
|
||||||
|
isPending,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="reward-link">
|
||||||
|
{isClaimed
|
||||||
|
? <span><Icon icon="icon-check" /> Reward claimed.</span>
|
||||||
|
: <Link
|
||||||
|
button={button ? button : "alt"}
|
||||||
|
disabled={isPending}
|
||||||
|
label={isPending ? "Claiming..." : "Claim Reward"}
|
||||||
|
onClick={() => {
|
||||||
|
claimReward(reward);
|
||||||
|
}}
|
||||||
|
/>}
|
||||||
|
{errorMessage
|
||||||
|
? <Modal
|
||||||
|
isOpen={true}
|
||||||
|
contentLabel="Reward Claim Error"
|
||||||
|
className="error-modal"
|
||||||
|
onConfirmed={() => {
|
||||||
|
clearError(reward);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{errorMessage}
|
||||||
|
</Modal>
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default RewardLink;
|
|
@ -1,14 +1,11 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { connect } from 'react-redux';
|
import { connect } from "react-redux";
|
||||||
import Router from './view.jsx';
|
import Router from "./view.jsx";
|
||||||
import {
|
import { selectCurrentPage, selectCurrentParams } from "selectors/app.js";
|
||||||
selectCurrentPage,
|
|
||||||
selectCurrentParams,
|
|
||||||
} from 'selectors/app.js';
|
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = state => ({
|
||||||
params: selectCurrentParams(state),
|
params: selectCurrentParams(state),
|
||||||
currentPage: selectCurrentPage(state)
|
currentPage: selectCurrentPage(state),
|
||||||
})
|
});
|
||||||
|
|
||||||
export default connect(select, null)(Router);
|
export default connect(select, null)(Router);
|
||||||
|
|
|
@ -1,51 +1,46 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import SettingsPage from 'page/settings';
|
import SettingsPage from "page/settings";
|
||||||
import HelpPage from 'page/help';
|
import HelpPage from "page/help";
|
||||||
import ReportPage from 'page/report.js';
|
import ReportPage from "page/report.js";
|
||||||
import StartPage from 'page/start.js';
|
import StartPage from "page/start.js";
|
||||||
import WalletPage from 'page/wallet';
|
import WalletPage from "page/wallet";
|
||||||
import ShowPage from 'page/showPage'
|
import ShowPage from "page/showPage";
|
||||||
import PublishPage from 'page/publish';
|
import PublishPage from "page/publish";
|
||||||
import DiscoverPage from 'page/discover';
|
import DiscoverPage from "page/discover";
|
||||||
import SplashScreen from 'component/splash.js';
|
import DeveloperPage from "page/developer.js";
|
||||||
import DeveloperPage from 'page/developer.js';
|
import RewardsPage from "page/rewards";
|
||||||
import RewardsPage from 'page/rewards.js';
|
import FileListDownloaded from "page/fileListDownloaded";
|
||||||
import FileListDownloaded from 'page/fileListDownloaded'
|
import FileListPublished from "page/fileListPublished";
|
||||||
import FileListPublished from 'page/fileListPublished'
|
import ChannelPage from "page/channel";
|
||||||
import ChannelPage from 'page/channel'
|
import SearchPage from "page/search";
|
||||||
import SearchPage from 'page/search'
|
|
||||||
|
|
||||||
const route = (page, routesMap) => {
|
const route = (page, routesMap) => {
|
||||||
const component = routesMap[page]
|
const component = routesMap[page];
|
||||||
|
|
||||||
return component
|
return component;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Router = props => {
|
||||||
const Router = (props) => {
|
const { currentPage, params } = props;
|
||||||
const {
|
|
||||||
currentPage,
|
|
||||||
params,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return route(currentPage, {
|
return route(currentPage, {
|
||||||
'settings': <SettingsPage {...params} />,
|
settings: <SettingsPage {...params} />,
|
||||||
'help': <HelpPage {...params} />,
|
help: <HelpPage {...params} />,
|
||||||
'report': <ReportPage {...params} />,
|
report: <ReportPage {...params} />,
|
||||||
'downloaded': <FileListDownloaded {...params} />,
|
downloaded: <FileListDownloaded {...params} />,
|
||||||
'published': <FileListPublished {...params} />,
|
published: <FileListPublished {...params} />,
|
||||||
'start': <StartPage {...params} />,
|
start: <StartPage {...params} />,
|
||||||
'wallet': <WalletPage {...params} />,
|
wallet: <WalletPage {...params} />,
|
||||||
'send': <WalletPage {...params} />,
|
send: <WalletPage {...params} />,
|
||||||
'receive': <WalletPage {...params} />,
|
receive: <WalletPage {...params} />,
|
||||||
'show': <ShowPage {...params} />,
|
show: <ShowPage {...params} />,
|
||||||
'channel': <ChannelPage {...params} />,
|
channel: <ChannelPage {...params} />,
|
||||||
'publish': <PublishPage {...params} />,
|
publish: <PublishPage {...params} />,
|
||||||
'developer': <DeveloperPage {...params} />,
|
developer: <DeveloperPage {...params} />,
|
||||||
'discover': <DiscoverPage {...params} />,
|
discover: <DiscoverPage {...params} />,
|
||||||
'rewards': <RewardsPage {...params} />,
|
rewards: <RewardsPage {...params} />,
|
||||||
'search': <SearchPage {...params} />,
|
search: <SearchPage {...params} />,
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Router
|
export default Router;
|
||||||
|
|
|
@ -1,23 +1,16 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect,
|
import { doNavigate, doRemoveSnackBarSnack } from "actions/app";
|
||||||
} from 'react-redux'
|
import { selectSnackBarSnacks } from "selectors/app";
|
||||||
import {
|
import SnackBar from "./view";
|
||||||
doNavigate,
|
|
||||||
doRemoveSnackBarSnack,
|
|
||||||
} from 'actions/app'
|
|
||||||
import {
|
|
||||||
selectSnackBarSnacks,
|
|
||||||
} from 'selectors/app'
|
|
||||||
import SnackBar from './view'
|
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
navigate: (path) => dispatch(doNavigate(path)),
|
navigate: path => dispatch(doNavigate(path)),
|
||||||
removeSnack: () => dispatch(doRemoveSnackBarSnack()),
|
removeSnack: () => dispatch(doRemoveSnackBarSnack()),
|
||||||
})
|
});
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = state => ({
|
||||||
snacks: selectSnackBarSnacks(state),
|
snacks: selectSnackBarSnacks(state),
|
||||||
})
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(SnackBar)
|
export default connect(select, perform)(SnackBar);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import Link from 'component/link'
|
import Link from "component/link";
|
||||||
|
|
||||||
class SnackBar extends React.Component {
|
class SnackBar extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
@ -10,11 +10,7 @@ class SnackBar extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { navigate, snacks, removeSnack } = this.props;
|
||||||
navigate,
|
|
||||||
snacks,
|
|
||||||
removeSnack,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
if (!snacks.length) {
|
if (!snacks.length) {
|
||||||
this._hideTimeout = null; //should be unmounting anyway, but be safe?
|
this._hideTimeout = null; //should be unmounting anyway, but be safe?
|
||||||
|
@ -22,25 +18,25 @@ class SnackBar extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const snack = snacks[0];
|
const snack = snacks[0];
|
||||||
const {
|
const { message, linkText, linkTarget } = snack;
|
||||||
message,
|
|
||||||
linkText,
|
|
||||||
linkTarget,
|
|
||||||
} = snack
|
|
||||||
|
|
||||||
if (this._hideTimeout === null) {
|
if (this._hideTimeout === null) {
|
||||||
this._hideTimeout = setTimeout(() => {
|
this._hideTimeout = setTimeout(() => {
|
||||||
this._hideTimeout = null;
|
this._hideTimeout = null;
|
||||||
removeSnack()
|
removeSnack();
|
||||||
}, this._displayTime * 1000);
|
}, this._displayTime * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="snack-bar">
|
<div className="snack-bar">
|
||||||
{message}
|
{message}
|
||||||
{linkText && linkTarget &&
|
{linkText &&
|
||||||
<Link onClick={() => navigate(linkTarget)} className="snack-bar__action" label={linkText} />
|
linkTarget &&
|
||||||
}
|
<Link
|
||||||
|
onClick={() => navigate(linkTarget)}
|
||||||
|
className="snack-bar__action"
|
||||||
|
label={linkText}
|
||||||
|
/>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +1,49 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import lbry from '../lbry.js';
|
import lbry from "../lbry.js";
|
||||||
import LoadScreen from './load_screen.js';
|
import LoadScreen from "./load_screen.js";
|
||||||
|
|
||||||
export class SplashScreen extends React.Component {
|
export class SplashScreen extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
message: React.PropTypes.string,
|
message: React.PropTypes.string,
|
||||||
onLoadDone: React.PropTypes.func,
|
onLoadDone: React.PropTypes.func,
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
details: 'Starting daemon',
|
details: __("Starting daemon"),
|
||||||
message: "Connecting",
|
message: __("Connecting"),
|
||||||
isLagging: false,
|
isLagging: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStatus() {
|
updateStatus() {
|
||||||
lbry.status().then((status) => { this._updateStatusCallback(status) });
|
lbry.status().then(status => {
|
||||||
|
this._updateStatusCallback(status);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateStatusCallback(status) {
|
_updateStatusCallback(status) {
|
||||||
const startupStatus = status.startup_status
|
const startupStatus = status.startup_status;
|
||||||
if (startupStatus.code == 'started') {
|
if (startupStatus.code == "started") {
|
||||||
// Wait until we are able to resolve a name before declaring
|
// Wait until we are able to resolve a name before declaring
|
||||||
// that we are done.
|
// that we are done.
|
||||||
// TODO: This is a hack, and the logic should live in the daemon
|
// 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
|
// to give us a better sense of when we are actually started
|
||||||
this.setState({
|
this.setState({
|
||||||
message: "Testing Network",
|
message: __("Testing Network"),
|
||||||
details: "Waiting for name resolution",
|
details: __("Waiting for name resolution"),
|
||||||
isLagging: false
|
isLagging: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
lbry.resolve({uri: "lbry://one"}).then(() => {
|
lbry.resolve({ uri: "lbry://one" }).then(() => {
|
||||||
this.props.onLoadDone();
|
this.props.onLoadDone();
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
details: startupStatus.message + (startupStatus.is_lagging ? '' : '...'),
|
details: startupStatus.message + (startupStatus.is_lagging ? "" : "..."),
|
||||||
isLagging: startupStatus.is_lagging,
|
isLagging: startupStatus.is_lagging,
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -50,19 +52,30 @@ export class SplashScreen extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
lbry.connect()
|
lbry
|
||||||
.then(() => { this.updateStatus() })
|
.connect()
|
||||||
|
.then(() => {
|
||||||
|
this.updateStatus();
|
||||||
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isLagging: true,
|
isLagging: true,
|
||||||
message: "Connection Failure",
|
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."
|
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() {
|
render() {
|
||||||
return <LoadScreen message={this.state.message} details={this.state.details} isWarning={this.state.isLagging} />
|
return (
|
||||||
|
<LoadScreen
|
||||||
|
message={this.state.message}
|
||||||
|
details={this.state.details}
|
||||||
|
isWarning={this.state.isLagging}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,16 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect,
|
import { selectCurrentPage, selectHeaderLinks } from "selectors/app";
|
||||||
} from 'react-redux'
|
import { doNavigate } from "actions/app";
|
||||||
import {
|
import SubHeader from "./view";
|
||||||
selectCurrentPage,
|
|
||||||
selectHeaderLinks,
|
|
||||||
} from 'selectors/app'
|
|
||||||
import {
|
|
||||||
doNavigate,
|
|
||||||
} from 'actions/app'
|
|
||||||
import SubHeader from './view'
|
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
currentPage: selectCurrentPage(state),
|
currentPage: selectCurrentPage(state),
|
||||||
subLinks: selectHeaderLinks(state),
|
subLinks: selectHeaderLinks(state),
|
||||||
})
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
navigate: (path) => dispatch(doNavigate(path)),
|
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 React from "react";
|
||||||
import Link from 'component/link'
|
import Link from "component/link";
|
||||||
|
|
||||||
const SubHeader = (props) => {
|
const SubHeader = props => {
|
||||||
const {
|
const { subLinks, currentPage, navigate, modifier } = props;
|
||||||
subLinks,
|
|
||||||
currentPage,
|
|
||||||
navigate,
|
|
||||||
modifier,
|
|
||||||
} = props
|
|
||||||
|
|
||||||
const links = []
|
const links = [];
|
||||||
|
|
||||||
for(let link of Object.keys(subLinks)) {
|
for (let link of Object.keys(subLinks)) {
|
||||||
links.push(
|
links.push(
|
||||||
<Link onClick={(event) => navigate(`/${link}`, event)} key={link} className={link == currentPage ? 'sub-header-selected' : 'sub-header-unselected' }>
|
<Link
|
||||||
|
onClick={event => navigate(`/${link}`, event)}
|
||||||
|
key={link}
|
||||||
|
className={
|
||||||
|
link == currentPage ? "sub-header-selected" : "sub-header-unselected"
|
||||||
|
}
|
||||||
|
>
|
||||||
{subLinks[link]}
|
{subLinks[link]}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className={'sub-header' + (modifier ? ' sub-header--' + modifier : '')}>
|
<nav
|
||||||
|
className={"sub-header" + (modifier ? " sub-header--" + modifier : "")}
|
||||||
|
>
|
||||||
{links}
|
{links}
|
||||||
</nav>
|
</nav>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default SubHeader
|
export default SubHeader;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
|
|
||||||
export class ToolTip extends React.Component {
|
export class ToolTip extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
body: React.PropTypes.string.isRequired,
|
body: React.PropTypes.string.isRequired,
|
||||||
label: React.PropTypes.string.isRequired
|
label: React.PropTypes.string.isRequired,
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -28,12 +28,23 @@ export class ToolTip extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<span className={'tooltip ' + (this.props.className || '')}>
|
<span className={"tooltip " + (this.props.className || "")}>
|
||||||
<a className="tooltip__link" onClick={() => { this.handleClick() }}>
|
<a
|
||||||
|
className="tooltip__link"
|
||||||
|
onClick={() => {
|
||||||
|
this.handleClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
{this.props.label}
|
{this.props.label}
|
||||||
</a>
|
</a>
|
||||||
<div className={'tooltip__body ' + (this.state.showTooltip ? '' : ' hidden')}
|
<div
|
||||||
onMouseOut={() => { this.handleTooltipMouseOut() }}>
|
className={
|
||||||
|
"tooltip__body " + (this.state.showTooltip ? "" : " hidden")
|
||||||
|
}
|
||||||
|
onMouseOut={() => {
|
||||||
|
this.handleTooltipMouseOut();
|
||||||
|
}}
|
||||||
|
>
|
||||||
{this.props.body}
|
{this.props.body}
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
@ -41,4 +52,4 @@ export class ToolTip extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ToolTip
|
export default ToolTip;
|
||||||
|
|
|
@ -1,25 +1,21 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect
|
import { doFetchTransactions } from "actions/wallet";
|
||||||
} from 'react-redux'
|
|
||||||
import {
|
|
||||||
doFetchTransactions,
|
|
||||||
} from 'actions/wallet'
|
|
||||||
import {
|
import {
|
||||||
selectBalance,
|
selectBalance,
|
||||||
selectTransactionItems,
|
selectTransactionItems,
|
||||||
selectIsFetchingTransactions,
|
selectIsFetchingTransactions,
|
||||||
} from 'selectors/wallet'
|
} from "selectors/wallet";
|
||||||
|
|
||||||
import TransactionList from './view'
|
import TransactionList from "./view";
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = state => ({
|
||||||
fetchingTransactions: selectIsFetchingTransactions(state),
|
fetchingTransactions: selectIsFetchingTransactions(state),
|
||||||
transactionItems: selectTransactionItems(state),
|
transactionItems: selectTransactionItems(state),
|
||||||
})
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
fetchTransactions: () => dispatch(doFetchTransactions())
|
fetchTransactions: () => dispatch(doFetchTransactions()),
|
||||||
})
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(TransactionList)
|
export default connect(select, perform)(TransactionList);
|
||||||
|
|
|
@ -1,31 +1,37 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import {
|
import { Address, BusyMessage, CreditAmount } from "component/common";
|
||||||
Address,
|
|
||||||
BusyMessage,
|
|
||||||
CreditAmount
|
|
||||||
} from 'component/common';
|
|
||||||
|
|
||||||
class TransactionList extends React.Component{
|
class TransactionList extends React.PureComponent {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.props.fetchTransactions()
|
this.props.fetchTransactions();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { fetchingTransactions, transactionItems } = this.props;
|
||||||
fetchingTransactions,
|
|
||||||
transactionItems,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
const rows = []
|
const rows = [];
|
||||||
if (transactionItems.length > 0) {
|
if (transactionItems.length > 0) {
|
||||||
transactionItems.forEach(function (item) {
|
transactionItems.forEach(function(item) {
|
||||||
rows.push(
|
rows.push(
|
||||||
<tr key={item.id}>
|
<tr key={item.id}>
|
||||||
<td>{ (item.amount > 0 ? '+' : '' ) + item.amount }</td>
|
<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>
|
<td>
|
||||||
<a className="button-text" href={"https://explorer.lbry.io/#!/transaction?id="+item.id}>{item.id.substr(0, 7)}</a>
|
{item.date
|
||||||
|
? item.date.toLocaleDateString()
|
||||||
|
: <span className="empty">{__("(Transaction pending)")}</span>}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{item.date
|
||||||
|
? item.date.toLocaleTimeString()
|
||||||
|
: <span className="empty">{__("(Transaction pending)")}</span>}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
className="button-text"
|
||||||
|
href={"https://explorer.lbry.io/#!/transaction?id=" + item.id}
|
||||||
|
>
|
||||||
|
{item.id.substr(0, 7)}
|
||||||
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
@ -35,31 +41,33 @@ class TransactionList extends React.Component{
|
||||||
return (
|
return (
|
||||||
<section className="card">
|
<section className="card">
|
||||||
<div className="card__title-primary">
|
<div className="card__title-primary">
|
||||||
<h3>Transaction History</h3>
|
<h3>{__("Transaction History")}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
{ fetchingTransactions && <BusyMessage message="Loading transactions" /> }
|
{fetchingTransactions &&
|
||||||
{ !fetchingTransactions && rows.length === 0 ? <div className="empty">You have no transactions.</div> : '' }
|
<BusyMessage message={__("Loading transactions")} />}
|
||||||
{ rows.length > 0 ?
|
{!fetchingTransactions && rows.length === 0
|
||||||
<table className="table-standard table-stretch">
|
? <div className="empty">{__("You have no transactions.")}</div>
|
||||||
|
: ""}
|
||||||
|
{rows.length > 0
|
||||||
|
? <table className="table-standard table-stretch">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Amount</th>
|
<th>{__("Amount")}</th>
|
||||||
<th>Date</th>
|
<th>{__("Date")}</th>
|
||||||
<th>Time</th>
|
<th>{__("Time")}</th>
|
||||||
<th>Transaction</th>
|
<th>{__("Transaction")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{rows}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
: ''
|
: ""}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TransactionList
|
export default TransactionList;
|
||||||
|
|
|
@ -1,19 +1,13 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect
|
import { doDownloadUpgrade, doSkipUpgrade } from "actions/app";
|
||||||
} from 'react-redux'
|
import UpgradeModal from "./view";
|
||||||
import {
|
|
||||||
doDownloadUpgrade,
|
|
||||||
doSkipUpgrade,
|
|
||||||
} from 'actions/app'
|
|
||||||
import UpgradeModal from './view'
|
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = state => ({});
|
||||||
})
|
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
downloadUpgrade: () => dispatch(doDownloadUpgrade()),
|
downloadUpgrade: () => dispatch(doDownloadUpgrade()),
|
||||||
skipUpgrade: () => dispatch(doSkipUpgrade()),
|
skipUpgrade: () => dispatch(doSkipUpgrade()),
|
||||||
})
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(UpgradeModal)
|
export default connect(select, perform)(UpgradeModal);
|
||||||
|
|
|
@ -1,32 +1,27 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { Modal } from "component/modal";
|
||||||
Modal
|
import { downloadUpgrade, skipUpgrade } from "actions/app";
|
||||||
} from 'component/modal'
|
|
||||||
import {
|
|
||||||
downloadUpgrade,
|
|
||||||
skipUpgrade
|
|
||||||
} from 'actions/app'
|
|
||||||
|
|
||||||
class UpgradeModal extends React.Component {
|
class UpgradeModal extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { downloadUpgrade, skipUpgrade } = this.props;
|
||||||
downloadUpgrade,
|
|
||||||
skipUpgrade
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
contentLabel="Update available"
|
contentLabel={__("Update available")}
|
||||||
type="confirm"
|
type="confirm"
|
||||||
confirmButtonLabel="Upgrade"
|
confirmButtonLabel={__("Upgrade")}
|
||||||
abortButtonLabel="Skip"
|
abortButtonLabel={__("Skip")}
|
||||||
onConfirmed={downloadUpgrade}
|
onConfirmed={downloadUpgrade}
|
||||||
onAborted={skipUpgrade}>
|
onAborted={skipUpgrade}
|
||||||
Your version of LBRY is out of date and may be unreliable or insecure.
|
>
|
||||||
|
{__(
|
||||||
|
"Your version of LBRY is out of date and may be unreliable or insecure."
|
||||||
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default UpgradeModal
|
export default UpgradeModal;
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import lbryuri from 'lbryuri';
|
import lbryuri from "lbryuri";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect,
|
import { makeSelectIsResolvingForUri } from "selectors/content";
|
||||||
} from 'react-redux'
|
import { makeSelectClaimForUri } from "selectors/claims";
|
||||||
import {
|
import UriIndicator from "./view";
|
||||||
makeSelectIsResolvingForUri
|
|
||||||
} from 'selectors/content'
|
|
||||||
import {
|
|
||||||
makeSelectClaimForUri,
|
|
||||||
} from 'selectors/claims'
|
|
||||||
import UriIndicator from './view'
|
|
||||||
|
|
||||||
const makeSelect = () => {
|
const makeSelect = () => {
|
||||||
const selectClaim = makeSelectClaimForUri(),
|
const selectClaim = makeSelectClaimForUri(),
|
||||||
|
@ -19,13 +13,13 @@ const makeSelect = () => {
|
||||||
claim: selectClaim(state, props),
|
claim: selectClaim(state, props),
|
||||||
isResolvingUri: selectIsResolving(state, props),
|
isResolvingUri: selectIsResolving(state, props),
|
||||||
uri: lbryuri.normalize(props.uri),
|
uri: lbryuri.normalize(props.uri),
|
||||||
})
|
});
|
||||||
|
|
||||||
return select
|
return select;
|
||||||
}
|
};
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
resolveUri: (uri) => dispatch(doResolveUri(uri))
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
})
|
});
|
||||||
|
|
||||||
export default connect(makeSelect, perform)(UriIndicator)
|
export default connect(makeSelect, perform)(UriIndicator);
|
||||||
|
|
|
@ -1,48 +1,39 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import {Icon} from 'component/common';
|
import { Icon } from "component/common";
|
||||||
|
|
||||||
class UriIndicator extends React.Component{
|
class UriIndicator extends React.PureComponent {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.resolve(this.props)
|
this.resolve(this.props);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
this.resolve(nextProps)
|
this.resolve(nextProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(props) {
|
resolve(props) {
|
||||||
const {
|
const { isResolvingUri, resolveUri, claim, uri } = props;
|
||||||
isResolvingUri,
|
|
||||||
resolveUri,
|
|
||||||
claim,
|
|
||||||
uri,
|
|
||||||
} = props
|
|
||||||
|
|
||||||
if(!isResolvingUri && claim === undefined && uri) {
|
if (!isResolvingUri && claim === undefined && uri) {
|
||||||
resolveUri(uri)
|
resolveUri(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { claim, uri, isResolvingUri } = this.props;
|
||||||
claim,
|
|
||||||
uri,
|
|
||||||
isResolvingUri
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
if (isResolvingUri) {
|
if (isResolvingUri) {
|
||||||
return <span className="empty">Validating...</span>
|
return <span className="empty">Validating...</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!claim) {
|
if (!claim) {
|
||||||
return <span className="empty">Unused</span>
|
return <span className="empty">Unused</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
channel_name: channelName,
|
channel_name: channelName,
|
||||||
has_signature: hasSignature,
|
has_signature: hasSignature,
|
||||||
signature_is_valid: signatureIsValid,
|
signature_is_valid: signatureIsValid,
|
||||||
} = claim
|
} = claim;
|
||||||
|
|
||||||
if (!hasSignature || !channelName) {
|
if (!hasSignature || !channelName) {
|
||||||
return <span className="empty">Anonymous</span>;
|
return <span className="empty">Anonymous</span>;
|
||||||
|
@ -50,20 +41,23 @@ class UriIndicator extends React.Component{
|
||||||
|
|
||||||
let icon, modifier;
|
let icon, modifier;
|
||||||
if (signatureIsValid) {
|
if (signatureIsValid) {
|
||||||
modifier = 'valid';
|
modifier = "valid";
|
||||||
} else {
|
} else {
|
||||||
icon = 'icon-times-circle';
|
icon = "icon-times-circle";
|
||||||
modifier = 'invalid';
|
modifier = "invalid";
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{channelName} {' '}
|
{channelName} {" "}
|
||||||
{ !signatureIsValid ?
|
{!signatureIsValid
|
||||||
<Icon icon={icon} className={`channel-indicator__icon channel-indicator__icon--${modifier}`} /> :
|
? <Icon
|
||||||
'' }
|
icon={icon}
|
||||||
|
className={`channel-indicator__icon channel-indicator__icon--${modifier}`}
|
||||||
|
/>
|
||||||
|
: ""}
|
||||||
</span>
|
</span>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
19
ui/js/component/userEmailNew/index.jsx
Normal file
19
ui/js/component/userEmailNew/index.jsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { doUserEmailNew } from "actions/user";
|
||||||
|
import {
|
||||||
|
selectEmailNewIsPending,
|
||||||
|
selectEmailNewErrorMessage,
|
||||||
|
} from "selectors/user";
|
||||||
|
import UserEmailNew from "./view";
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
isPending: selectEmailNewIsPending(state),
|
||||||
|
errorMessage: selectEmailNewErrorMessage(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
addUserEmail: email => dispatch(doUserEmailNew(email)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(UserEmailNew);
|
61
ui/js/component/userEmailNew/view.jsx
Normal file
61
ui/js/component/userEmailNew/view.jsx
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import React from "react";
|
||||||
|
import Link from "component/link";
|
||||||
|
import { FormRow } from "component/form.js";
|
||||||
|
|
||||||
|
class UserEmailNew extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
email: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEmailChanged(event) {
|
||||||
|
this.setState({
|
||||||
|
email: event.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.props.addUserEmail(this.state.email);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { errorMessage, isPending } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className="form-input-width"
|
||||||
|
onSubmit={event => {
|
||||||
|
this.handleSubmit(event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormRow
|
||||||
|
type="text"
|
||||||
|
label="Email"
|
||||||
|
placeholder="scrwvwls@lbry.io"
|
||||||
|
name="email"
|
||||||
|
value={this.state.email}
|
||||||
|
errorMessage={errorMessage}
|
||||||
|
onChange={event => {
|
||||||
|
this.handleEmailChanged(event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="form-row-submit">
|
||||||
|
<Link
|
||||||
|
button="primary"
|
||||||
|
label="Next"
|
||||||
|
disabled={isPending}
|
||||||
|
onClick={event => {
|
||||||
|
this.handleSubmit(event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserEmailNew;
|
21
ui/js/component/userEmailVerify/index.jsx
Normal file
21
ui/js/component/userEmailVerify/index.jsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { doUserEmailVerify } from "actions/user";
|
||||||
|
import {
|
||||||
|
selectEmailVerifyIsPending,
|
||||||
|
selectEmailToVerify,
|
||||||
|
selectEmailVerifyErrorMessage,
|
||||||
|
} from "selectors/user";
|
||||||
|
import UserEmailVerify from "./view";
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
isPending: selectEmailVerifyIsPending(state),
|
||||||
|
email: selectEmailToVerify(state),
|
||||||
|
errorMessage: selectEmailVerifyErrorMessage(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
verifyUserEmail: code => dispatch(doUserEmailVerify(code)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(UserEmailVerify);
|
68
ui/js/component/userEmailVerify/view.jsx
Normal file
68
ui/js/component/userEmailVerify/view.jsx
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import React from "react";
|
||||||
|
import Link from "component/link";
|
||||||
|
import { FormRow } from "component/form.js";
|
||||||
|
|
||||||
|
class UserEmailVerify extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
code: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCodeChanged(event) {
|
||||||
|
this.setState({
|
||||||
|
code: event.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.props.verifyUserEmail(this.state.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { errorMessage, isPending } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className="form-input-width"
|
||||||
|
onSubmit={event => {
|
||||||
|
this.handleSubmit(event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormRow
|
||||||
|
type="text"
|
||||||
|
label="Verification Code"
|
||||||
|
placeholder="a94bXXXXXXXXXXXXXX"
|
||||||
|
name="code"
|
||||||
|
value={this.state.code}
|
||||||
|
onChange={event => {
|
||||||
|
this.handleCodeChanged(event);
|
||||||
|
}}
|
||||||
|
errorMessage={errorMessage}
|
||||||
|
/>
|
||||||
|
{/* render help separately so it always shows */}
|
||||||
|
<div className="form-field__helper">
|
||||||
|
<p>
|
||||||
|
Email <Link href="mailto:help@lbry.io" label="help@lbry.io" /> if
|
||||||
|
you did not receive or are having trouble with your code.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserEmailVerify;
|
|
@ -1,39 +1,27 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect,
|
import { doCloseModal } from "actions/app";
|
||||||
} from 'react-redux'
|
import { selectCurrentModal } from "selectors/app";
|
||||||
import {
|
import { doPurchaseUri, doLoadVideo } from "actions/content";
|
||||||
doCloseModal,
|
|
||||||
} from 'actions/app'
|
|
||||||
import {
|
|
||||||
selectCurrentModal,
|
|
||||||
} from 'selectors/app'
|
|
||||||
import {
|
|
||||||
doPurchaseUri,
|
|
||||||
doLoadVideo,
|
|
||||||
} from 'actions/content'
|
|
||||||
import {
|
import {
|
||||||
makeSelectMetadataForUri,
|
makeSelectMetadataForUri,
|
||||||
makeSelectContentTypeForUri,
|
makeSelectContentTypeForUri,
|
||||||
} from 'selectors/claims'
|
} from "selectors/claims";
|
||||||
import {
|
import {
|
||||||
makeSelectFileInfoForUri,
|
makeSelectFileInfoForUri,
|
||||||
makeSelectLoadingForUri,
|
makeSelectLoadingForUri,
|
||||||
makeSelectDownloadingForUri,
|
makeSelectDownloadingForUri,
|
||||||
} from 'selectors/file_info'
|
} from "selectors/file_info";
|
||||||
import {
|
import { makeSelectCostInfoForUri } from "selectors/cost_info";
|
||||||
makeSelectCostInfoForUri,
|
import Video from "./view";
|
||||||
} from 'selectors/cost_info'
|
|
||||||
import Video from './view'
|
|
||||||
|
|
||||||
|
|
||||||
const makeSelect = () => {
|
const makeSelect = () => {
|
||||||
const selectCostInfo = makeSelectCostInfoForUri()
|
const selectCostInfo = makeSelectCostInfoForUri();
|
||||||
const selectFileInfo = makeSelectFileInfoForUri()
|
const selectFileInfo = makeSelectFileInfoForUri();
|
||||||
const selectIsLoading = makeSelectLoadingForUri()
|
const selectIsLoading = makeSelectLoadingForUri();
|
||||||
const selectIsDownloading = makeSelectDownloadingForUri()
|
const selectIsDownloading = makeSelectDownloadingForUri();
|
||||||
const selectMetadata = makeSelectMetadataForUri()
|
const selectMetadata = makeSelectMetadataForUri();
|
||||||
const selectContentType = makeSelectContentTypeForUri()
|
const selectContentType = makeSelectContentTypeForUri();
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
costInfo: selectCostInfo(state, props),
|
costInfo: selectCostInfo(state, props),
|
||||||
|
@ -43,15 +31,15 @@ const makeSelect = () => {
|
||||||
isLoading: selectIsLoading(state, props),
|
isLoading: selectIsLoading(state, props),
|
||||||
isDownloading: selectIsDownloading(state, props),
|
isDownloading: selectIsDownloading(state, props),
|
||||||
contentType: selectContentType(state, props),
|
contentType: selectContentType(state, props),
|
||||||
})
|
});
|
||||||
|
|
||||||
return select
|
return select;
|
||||||
}
|
};
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
loadVideo: (uri) => dispatch(doLoadVideo(uri)),
|
loadVideo: uri => dispatch(doLoadVideo(uri)),
|
||||||
purchaseUri: (uri) => dispatch(doPurchaseUri(uri, 'affirmPurchaseAndPlay')),
|
purchaseUri: uri => dispatch(doPurchaseUri(uri, "affirmPurchaseAndPlay")),
|
||||||
closeModal: () => dispatch(doCloseModal()),
|
closeModal: () => dispatch(doCloseModal()),
|
||||||
})
|
});
|
||||||
|
|
||||||
export default connect(makeSelect, perform)(Video)
|
export default connect(makeSelect, perform)(Video);
|
||||||
|
|
14
ui/js/component/video/internal/loading-screen.jsx
Normal file
14
ui/js/component/video/internal/loading-screen.jsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const LoadingScreen = ({ status, spinner = true }) =>
|
||||||
|
<div className="video__loading-screen">
|
||||||
|
<div>
|
||||||
|
{spinner && <div className="video__loading-spinner" />}
|
||||||
|
|
||||||
|
<div className="video__loading-status">
|
||||||
|
{status}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
|
||||||
|
export default LoadingScreen;
|
92
ui/js/component/video/internal/play-button.jsx
Normal file
92
ui/js/component/video/internal/play-button.jsx
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import React from "react";
|
||||||
|
import FilePrice from "component/filePrice";
|
||||||
|
import Link from "component/link";
|
||||||
|
import Modal from "component/modal";
|
||||||
|
|
||||||
|
class VideoPlayButton extends React.PureComponent {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
button,
|
||||||
|
label,
|
||||||
|
metadata,
|
||||||
|
metadata: { title },
|
||||||
|
uri,
|
||||||
|
modal,
|
||||||
|
closeModal,
|
||||||
|
isLoading,
|
||||||
|
costInfo,
|
||||||
|
fileInfo,
|
||||||
|
mediaType,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
/*
|
||||||
|
title={
|
||||||
|
isLoading ? "Video is Loading" :
|
||||||
|
!costInfo ? "Waiting on cost info..." :
|
||||||
|
fileInfo === undefined ? "Waiting on file info..." : ""
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VideoPlayButton;
|
102
ui/js/component/video/internal/player.jsx
Normal file
102
ui/js/component/video/internal/player.jsx
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Thumbnail } from "component/common";
|
||||||
|
import player from "render-media";
|
||||||
|
import fs from "fs";
|
||||||
|
import LoadingScreen from "./loading-screen";
|
||||||
|
|
||||||
|
class VideoPlayer extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
hasMetadata: false,
|
||||||
|
startedPlaying: false,
|
||||||
|
unplayable: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const container = this.refs.media;
|
||||||
|
const { mediaType } = this.props;
|
||||||
|
const loadedMetadata = e => {
|
||||||
|
this.setState({ hasMetadata: true, startedPlaying: true });
|
||||||
|
this.refs.media.children[0].play();
|
||||||
|
};
|
||||||
|
const renderMediaCallback = err => {
|
||||||
|
if (err) this.setState({ unplayable: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
player.append(
|
||||||
|
this.file(),
|
||||||
|
container,
|
||||||
|
{ autoplay: false, controls: true },
|
||||||
|
renderMediaCallback.bind(this)
|
||||||
|
);
|
||||||
|
|
||||||
|
const mediaElement = this.refs.media.children[0];
|
||||||
|
if (mediaElement) {
|
||||||
|
mediaElement.addEventListener(
|
||||||
|
"loadedmetadata",
|
||||||
|
loadedMetadata.bind(this),
|
||||||
|
{
|
||||||
|
once: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
const { mediaType, downloadCompleted } = this.props;
|
||||||
|
const { startedPlaying } = this.state;
|
||||||
|
|
||||||
|
if (this.playableType() && !startedPlaying && downloadCompleted) {
|
||||||
|
const container = this.refs.media.children[0];
|
||||||
|
|
||||||
|
player.render(this.file(), container, { autoplay: true, controls: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file() {
|
||||||
|
const { downloadPath, filename } = this.props;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: filename,
|
||||||
|
createReadStream: opts => {
|
||||||
|
return fs.createReadStream(downloadPath, opts);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
playableType() {
|
||||||
|
const { mediaType } = this.props;
|
||||||
|
|
||||||
|
return ["audio", "video"].indexOf(mediaType) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { mediaType, poster } = this.props;
|
||||||
|
const { hasMetadata, unplayable } = this.state;
|
||||||
|
const noMetadataMessage = "Waiting for metadata.";
|
||||||
|
const unplayableMessage = "Sorry, looks like we can't play this file.";
|
||||||
|
|
||||||
|
const needsMetadata = this.playableType();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{["audio", "application"].indexOf(mediaType) !== -1 &&
|
||||||
|
(!this.playableType() || hasMetadata) &&
|
||||||
|
!unplayable &&
|
||||||
|
<Thumbnail src={poster} className="video-embedded" />}
|
||||||
|
{this.playableType() &&
|
||||||
|
!hasMetadata &&
|
||||||
|
!unplayable &&
|
||||||
|
<LoadingScreen status={noMetadataMessage} />}
|
||||||
|
{unplayable &&
|
||||||
|
<LoadingScreen status={unplayableMessage} spinner={false} />}
|
||||||
|
<div ref="media" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VideoPlayer;
|
|
@ -1,92 +1,19 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import FilePrice from 'component/filePrice'
|
import lbry from "lbry";
|
||||||
import Link from 'component/link';
|
import VideoPlayer from "./internal/player";
|
||||||
import Modal from 'component/modal';
|
import VideoPlayButton from "./internal/play-button";
|
||||||
import lbry from 'lbry'
|
import LoadingScreen from "./internal/loading-screen";
|
||||||
import {
|
|
||||||
Thumbnail,
|
|
||||||
} from 'component/common'
|
|
||||||
|
|
||||||
class VideoPlayButton extends React.Component {
|
class Video extends React.PureComponent {
|
||||||
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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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..." :
|
|
||||||
fileInfo === undefined ? "Waiting on file info..." : ""
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const disabled = isLoading || fileInfo === undefined || (fileInfo === null && (!costInfo || costInfo.cost === undefined))
|
|
||||||
const icon = mediaType == "image" ? "icon-folder-o" : "icon-play"
|
|
||||||
|
|
||||||
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) {
|
constructor(props) {
|
||||||
super(props)
|
super(props);
|
||||||
this.state = { isPlaying: false }
|
this.state = { isPlaying: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
startPlaying() {
|
startPlaying() {
|
||||||
this.setState({
|
this.setState({
|
||||||
isPlaying: true
|
isPlaying: true,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -96,85 +23,67 @@ class Video extends React.Component {
|
||||||
isDownloading,
|
isDownloading,
|
||||||
fileInfo,
|
fileInfo,
|
||||||
contentType,
|
contentType,
|
||||||
} = this.props
|
} = this.props;
|
||||||
const {
|
const { isPlaying = false } = this.state;
|
||||||
isPlaying = false,
|
|
||||||
} = this.state
|
|
||||||
|
|
||||||
const isReadyToPlay = fileInfo && fileInfo.written_bytes > 0
|
const isReadyToPlay = fileInfo && fileInfo.written_bytes > 0;
|
||||||
const mediaType = lbry.getMediaType(contentType, fileInfo && fileInfo.file_name)
|
const mediaType = lbry.getMediaType(
|
||||||
|
contentType,
|
||||||
|
fileInfo && fileInfo.file_name
|
||||||
|
);
|
||||||
|
|
||||||
let loadStatusMessage = ''
|
let loadStatusMessage = "";
|
||||||
|
|
||||||
if(fileInfo && fileInfo.completed && !fileInfo.written_bytes) {
|
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."
|
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) {
|
} else if (isLoading) {
|
||||||
loadStatusMessage = "Requesting stream... it may sit here for like 15-20 seconds in a really awkward way... we're working on it"
|
loadStatusMessage = __(
|
||||||
|
"Requesting stream... it may sit here for like 15-20 seconds in a really awkward way... we're working on it"
|
||||||
|
);
|
||||||
} else if (isDownloading) {
|
} else if (isDownloading) {
|
||||||
loadStatusMessage = "Downloading stream... not long left now!"
|
loadStatusMessage = __("Downloading stream... not long left now!");
|
||||||
}
|
}
|
||||||
|
|
||||||
let klassName = ""
|
let klassName = "";
|
||||||
if (isLoading || isDownloading) klassName += "video-embedded video"
|
if (isLoading || isDownloading) klassName += "video-embedded video";
|
||||||
if (mediaType === "video") {
|
if (mediaType === "video") {
|
||||||
klassName += "video-embedded video"
|
klassName += "video-embedded video";
|
||||||
klassName += isPlaying ? " video--active" : " video--hidden"
|
klassName += isPlaying ? " video--active" : " video--hidden";
|
||||||
|
} else if (mediaType === "application") {
|
||||||
|
klassName += "video-embedded";
|
||||||
} else {
|
} else {
|
||||||
if (!isPlaying) klassName += "video-embedded"
|
if (!isPlaying) klassName += "video-embedded";
|
||||||
}
|
}
|
||||||
const poster = metadata.thumbnail
|
const poster = metadata.thumbnail;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={klassName}>{
|
<div className={klassName}>
|
||||||
isPlaying ?
|
{isPlaying &&
|
||||||
(!isReadyToPlay ?
|
(!isReadyToPlay
|
||||||
<span>this is the world's worst loading screen and we shipped our software with it anyway... <br /><br />{loadStatusMessage}</span> :
|
? <LoadingScreen status={loadStatusMessage} />
|
||||||
<VideoPlayer filename={fileInfo.file_name} poster={poster} downloadPath={fileInfo.download_path} mediaType={mediaType} poster={poster} />) :
|
: <VideoPlayer
|
||||||
<div className="video__cover" style={{backgroundImage: 'url("' + metadata.thumbnail + '")'}}>
|
filename={fileInfo.file_name}
|
||||||
<VideoPlayButton startPlaying={this.startPlaying.bind(this)} {...this.props} mediaType={mediaType} />
|
poster={poster}
|
||||||
</div>
|
downloadPath={fileInfo.download_path}
|
||||||
}</div>
|
mediaType={mediaType}
|
||||||
|
downloadCompleted={fileInfo.completed}
|
||||||
|
/>)}
|
||||||
|
{!isPlaying &&
|
||||||
|
<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')
|
export default Video;
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
downloadPath,
|
|
||||||
mediaType,
|
|
||||||
poster,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{mediaType === "audio" && <Thumbnail src={poster} className="video-embedded" />}
|
|
||||||
<div ref="media" />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Video
|
|
||||||
|
|
|
@ -1,25 +1,20 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect
|
import { doCheckAddressIsMine, doGetNewAddress } from "actions/wallet";
|
||||||
} from 'react-redux'
|
|
||||||
import {
|
|
||||||
doCheckAddressIsMine,
|
|
||||||
doGetNewAddress,
|
|
||||||
} from 'actions/wallet'
|
|
||||||
import {
|
import {
|
||||||
selectReceiveAddress,
|
selectReceiveAddress,
|
||||||
selectGettingNewAddress
|
selectGettingNewAddress,
|
||||||
} from 'selectors/wallet'
|
} from "selectors/wallet";
|
||||||
import WalletPage from './view'
|
import WalletPage from "./view";
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = state => ({
|
||||||
receiveAddress: selectReceiveAddress(state),
|
receiveAddress: selectReceiveAddress(state),
|
||||||
gettingNewAddress: selectGettingNewAddress(state),
|
gettingNewAddress: selectGettingNewAddress(state),
|
||||||
})
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
checkAddressIsMine: (address) => dispatch(doCheckAddressIsMine(address)),
|
checkAddressIsMine: address => dispatch(doCheckAddressIsMine(address)),
|
||||||
getNewAddress: () => dispatch(doGetNewAddress()),
|
getNewAddress: () => dispatch(doGetNewAddress()),
|
||||||
})
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(WalletPage)
|
export default connect(select, perform)(WalletPage);
|
||||||
|
|
|
@ -1,36 +1,44 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import Link from 'component/link';
|
import Link from "component/link";
|
||||||
import {
|
import { Address } from "component/common";
|
||||||
Address
|
|
||||||
} from 'component/common';
|
|
||||||
|
|
||||||
class WalletAddress extends React.Component {
|
class WalletAddress extends React.PureComponent {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.props.checkAddressIsMine(this.props.receiveAddress)
|
this.props.checkAddressIsMine(this.props.receiveAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { receiveAddress, getNewAddress, gettingNewAddress } = this.props;
|
||||||
receiveAddress,
|
|
||||||
getNewAddress,
|
|
||||||
gettingNewAddress,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="card">
|
<section className="card">
|
||||||
<div className="card__title-primary">
|
<div className="card__title-primary">
|
||||||
<h3>Wallet Address</h3>
|
<h3>{__("Wallet Address")}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
<Address address={receiveAddress} />
|
<Address address={receiveAddress} />
|
||||||
</div>
|
</div>
|
||||||
<div className="card__actions">
|
<div className="card__actions">
|
||||||
<Link label="Get New Address" button="primary" icon='icon-refresh' onClick={getNewAddress} disabled={gettingNewAddress} />
|
<Link
|
||||||
|
label={__("Get New Address")}
|
||||||
|
button="primary"
|
||||||
|
icon="icon-refresh"
|
||||||
|
onClick={getNewAddress}
|
||||||
|
disabled={gettingNewAddress}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
<div className="help">
|
<div className="help">
|
||||||
<p>Other LBRY users may send credits to you by entering this address on the "Send" page.</p>
|
<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>
|
{__(
|
||||||
|
'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>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -38,4 +46,4 @@ class WalletAddress extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WalletAddress
|
export default WalletAddress;
|
||||||
|
|
|
@ -1,36 +1,31 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect
|
import { doCloseModal } from "actions/app";
|
||||||
} from 'react-redux'
|
|
||||||
import {
|
|
||||||
doCloseModal,
|
|
||||||
} from 'actions/app'
|
|
||||||
import {
|
import {
|
||||||
doSendDraftTransaction,
|
doSendDraftTransaction,
|
||||||
doSetDraftTransactionAmount,
|
doSetDraftTransactionAmount,
|
||||||
doSetDraftTransactionAddress,
|
doSetDraftTransactionAddress,
|
||||||
} from 'actions/wallet'
|
} from "actions/wallet";
|
||||||
import {
|
import { selectCurrentModal } from "selectors/app";
|
||||||
selectCurrentModal,
|
|
||||||
} from 'selectors/app'
|
|
||||||
import {
|
import {
|
||||||
selectDraftTransactionAmount,
|
selectDraftTransactionAmount,
|
||||||
selectDraftTransactionAddress,
|
selectDraftTransactionAddress,
|
||||||
} from 'selectors/wallet'
|
} from "selectors/wallet";
|
||||||
|
|
||||||
import WalletSend from './view'
|
import WalletSend from "./view";
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = state => ({
|
||||||
modal: selectCurrentModal(state),
|
modal: selectCurrentModal(state),
|
||||||
address: selectDraftTransactionAddress(state),
|
address: selectDraftTransactionAddress(state),
|
||||||
amount: selectDraftTransactionAmount(state),
|
amount: selectDraftTransactionAmount(state),
|
||||||
})
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
closeModal: () => dispatch(doCloseModal()),
|
closeModal: () => dispatch(doCloseModal()),
|
||||||
sendToAddress: () => dispatch(doSendDraftTransaction()),
|
sendToAddress: () => dispatch(doSendDraftTransaction()),
|
||||||
setAmount: (event) => dispatch(doSetDraftTransactionAmount(event.target.value)),
|
setAmount: event => dispatch(doSetDraftTransactionAmount(event.target.value)),
|
||||||
setAddress: (event) => dispatch(doSetDraftTransactionAddress(event.target.value)),
|
setAddress: event =>
|
||||||
})
|
dispatch(doSetDraftTransactionAddress(event.target.value)),
|
||||||
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(WalletSend)
|
export default connect(select, perform)(WalletSend);
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import Link from 'component/link';
|
import Link from "component/link";
|
||||||
import Modal from 'component/modal';
|
import Modal from "component/modal";
|
||||||
import {
|
import { FormRow } from "component/form";
|
||||||
FormRow
|
|
||||||
} from 'component/form';
|
|
||||||
|
|
||||||
const WalletSend = (props) => {
|
const WalletSend = props => {
|
||||||
const {
|
const {
|
||||||
sendToAddress,
|
sendToAddress,
|
||||||
closeModal,
|
closeModal,
|
||||||
|
@ -14,36 +12,74 @@ const WalletSend = (props) => {
|
||||||
setAddress,
|
setAddress,
|
||||||
amount,
|
amount,
|
||||||
address,
|
address,
|
||||||
} = props
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="card">
|
<section className="card">
|
||||||
<form onSubmit={sendToAddress}>
|
<form onSubmit={sendToAddress}>
|
||||||
<div className="card__title-primary">
|
<div className="card__title-primary">
|
||||||
<h3>Send Credits</h3>
|
<h3>{__("Send Credits")}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
<FormRow label="Amount" postfix="LBC" step="0.01" type="number" placeholder="1.23" size="10" onChange={setAmount} value={amount} />
|
<FormRow
|
||||||
|
label={__("Amount")}
|
||||||
|
postfix="LBC"
|
||||||
|
step="0.01"
|
||||||
|
type="number"
|
||||||
|
placeholder="1.23"
|
||||||
|
size="10"
|
||||||
|
onChange={setAmount}
|
||||||
|
value={amount}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
<FormRow label="Recipient Address" placeholder="bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs" type="text" size="60" onChange={setAddress} value={address} />
|
<FormRow
|
||||||
|
label={__("Recipient Address")}
|
||||||
|
placeholder="bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs"
|
||||||
|
type="text"
|
||||||
|
size="60"
|
||||||
|
onChange={setAddress}
|
||||||
|
value={address}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="card__actions card__actions--form-submit">
|
<div className="card__actions card__actions--form-submit">
|
||||||
<Link button="primary" label="Send" onClick={sendToAddress} disabled={!(parseFloat(amount) > 0.0) || !address} />
|
<Link
|
||||||
<input type='submit' className='hidden' />
|
button="primary"
|
||||||
|
label={__("Send")}
|
||||||
|
onClick={sendToAddress}
|
||||||
|
disabled={!(parseFloat(amount) > 0.0) || !address}
|
||||||
|
/>
|
||||||
|
<input type="submit" className="hidden" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{modal == 'insufficientBalance' && <Modal isOpen={true} contentLabel="Insufficient balance" onConfirmed={closeModal}>
|
{modal == "insufficientBalance" &&
|
||||||
Insufficient balance: after this transaction you would have less than 1 LBC in your wallet.
|
<Modal
|
||||||
</Modal>}
|
isOpen={true}
|
||||||
{modal == 'transactionSuccessful' && <Modal isOpen={true} contentLabel="Transaction successful" onConfirmed={closeModal}>
|
contentLabel={__("Insufficient balance")}
|
||||||
Your transaction was successfully placed in the queue.
|
onConfirmed={closeModal}
|
||||||
</Modal>}
|
>
|
||||||
{modal == 'transactionFailed' && <Modal isOpen={true} contentLabel="Transaction failed" onConfirmed={closeModal}>
|
{__(
|
||||||
Something went wrong:
|
"Insufficient balance: after this transaction you would have less than 1 LBC in your wallet."
|
||||||
</Modal>}
|
)}
|
||||||
|
</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>
|
</section>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default WalletSend
|
export default WalletSend;
|
||||||
|
|
28
ui/js/component/welcomeModal/index.jsx
Normal file
28
ui/js/component/welcomeModal/index.jsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import React from "react";
|
||||||
|
import rewards from "rewards";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { doCloseModal } from "actions/app";
|
||||||
|
import { selectUserIsRewardApproved } from "selectors/user";
|
||||||
|
import {
|
||||||
|
makeSelectHasClaimedReward,
|
||||||
|
makeSelectClaimRewardError,
|
||||||
|
makeSelectRewardByType,
|
||||||
|
} from "selectors/rewards";
|
||||||
|
import WelcomeModal from "./view";
|
||||||
|
|
||||||
|
const select = (state, props) => {
|
||||||
|
const selectHasClaimed = makeSelectHasClaimedReward(),
|
||||||
|
selectReward = makeSelectRewardByType();
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasClaimed: selectHasClaimed(state, { reward_type: rewards.TYPE_NEW_USER }),
|
||||||
|
isRewardApproved: selectUserIsRewardApproved(state),
|
||||||
|
reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
closeModal: () => dispatch(doCloseModal()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(WelcomeModal);
|
75
ui/js/component/welcomeModal/view.jsx
Normal file
75
ui/js/component/welcomeModal/view.jsx
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Modal } from "component/modal";
|
||||||
|
import { CreditAmount } from "component/common";
|
||||||
|
import Link from "component/link";
|
||||||
|
import RewardLink from "component/rewardLink";
|
||||||
|
|
||||||
|
class WelcomeModal extends React.PureComponent {
|
||||||
|
render() {
|
||||||
|
const { closeModal, hasClaimed, isRewardApproved, reward } = this.props;
|
||||||
|
|
||||||
|
return !hasClaimed
|
||||||
|
? <Modal type="custom" isOpen={true} contentLabel="Welcome to LBRY">
|
||||||
|
<section>
|
||||||
|
<h3 className="modal__header">Welcome to LBRY.</h3>
|
||||||
|
<p>
|
||||||
|
Using LBRY is like dating a centaur. Totally normal up top, and
|
||||||
|
{" "}<em>way different</em> 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!
|
||||||
|
{" "}{isRewardApproved ? __("Here's a nickel, kid.") : ""}
|
||||||
|
</p>
|
||||||
|
<div className="text-center">
|
||||||
|
{isRewardApproved
|
||||||
|
? <RewardLink reward_type="new_user" button="primary" />
|
||||||
|
: <Link
|
||||||
|
button="primary"
|
||||||
|
onClick={closeModal}
|
||||||
|
label="Continue"
|
||||||
|
/>}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Modal>
|
||||||
|
: <Modal
|
||||||
|
type="alert"
|
||||||
|
overlayClassName="modal-overlay modal-overlay--clear"
|
||||||
|
isOpen={true}
|
||||||
|
contentLabel="Welcome to LBRY"
|
||||||
|
onConfirmed={closeModal}
|
||||||
|
>
|
||||||
|
<section>
|
||||||
|
<h3 className="modal__header">About Your Reward</h3>
|
||||||
|
<p>
|
||||||
|
You earned a reward of
|
||||||
|
{" "}<CreditAmount amount={reward.reward_amount} label={false} />
|
||||||
|
{" "}LBRY
|
||||||
|
credits, or <em>LBC</em>.
|
||||||
|
</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>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WelcomeModal;
|
|
@ -1,25 +1,19 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {
|
import { connect } from "react-redux";
|
||||||
connect
|
import lbryuri from "lbryuri.js";
|
||||||
} from 'react-redux'
|
import { selectWunderBarAddress, selectWunderBarIcon } from "selectors/search";
|
||||||
import lbryuri from 'lbryuri.js'
|
import { doNavigate } from "actions/app";
|
||||||
import {
|
import Wunderbar from "./view";
|
||||||
selectWunderBarAddress,
|
|
||||||
selectWunderBarIcon
|
|
||||||
} from 'selectors/search'
|
|
||||||
import {
|
|
||||||
doNavigate,
|
|
||||||
} from 'actions/app'
|
|
||||||
import Wunderbar from './view'
|
|
||||||
|
|
||||||
const select = (state) => ({
|
const select = state => ({
|
||||||
address: selectWunderBarAddress(state),
|
address: selectWunderBarAddress(state),
|
||||||
icon: selectWunderBarIcon(state)
|
icon: selectWunderBarIcon(state),
|
||||||
})
|
});
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = dispatch => ({
|
||||||
onSearch: (query) => dispatch(doNavigate('/search', { query, })),
|
onSearch: query => dispatch(doNavigate("/search", { query })),
|
||||||
onSubmit: (query) => dispatch(doNavigate('/show', { uri: lbryuri.normalize(query) } ))
|
onSubmit: query =>
|
||||||
})
|
dispatch(doNavigate("/show", { uri: lbryuri.normalize(query) })),
|
||||||
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(Wunderbar)
|
export default connect(select, perform)(Wunderbar);
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import lbryuri from 'lbryuri.js';
|
import lbryuri from "lbryuri.js";
|
||||||
import {Icon} from 'component/common.js';
|
import { Icon } from "component/common.js";
|
||||||
|
|
||||||
class WunderBar extends React.PureComponent {
|
class WunderBar extends React.PureComponent {
|
||||||
static TYPING_TIMEOUT = 800
|
static TYPING_TIMEOUT = 800;
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onSearch: React.PropTypes.func.isRequired,
|
onSearch: React.PropTypes.func.isRequired,
|
||||||
onSubmit: React.PropTypes.func.isRequired
|
onSubmit: React.PropTypes.func.isRequired,
|
||||||
}
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -24,7 +24,7 @@ class WunderBar extends React.PureComponent {
|
||||||
this.onReceiveRef = this.onReceiveRef.bind(this);
|
this.onReceiveRef = this.onReceiveRef.bind(this);
|
||||||
this.state = {
|
this.state = {
|
||||||
address: this.props.address,
|
address: this.props.address,
|
||||||
icon: this.props.icon
|
icon: this.props.icon,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,13 +35,11 @@ class WunderBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange(event) {
|
onChange(event) {
|
||||||
|
if (this._userTypingTimer) {
|
||||||
if (this._userTypingTimer)
|
|
||||||
{
|
|
||||||
clearTimeout(this._userTypingTimer);
|
clearTimeout(this._userTypingTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ address: event.target.value })
|
this.setState({ address: event.target.value });
|
||||||
|
|
||||||
this._isSearchDispatchPending = true;
|
this._isSearchDispatchPending = true;
|
||||||
|
|
||||||
|
@ -58,7 +56,10 @@ class WunderBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (nextProps.viewingPage !== this.props.viewingPage || nextProps.address != this.props.address) {
|
if (
|
||||||
|
nextProps.viewingPage !== this.props.viewingPage ||
|
||||||
|
nextProps.address != this.props.address
|
||||||
|
) {
|
||||||
this.setState({ address: nextProps.address, icon: nextProps.icon });
|
this.setState({ address: nextProps.address, icon: nextProps.icon });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,14 +68,17 @@ class WunderBar extends React.PureComponent {
|
||||||
this._stateBeforeSearch = this.state;
|
this._stateBeforeSearch = this.state;
|
||||||
let newState = {
|
let newState = {
|
||||||
icon: "icon-search",
|
icon: "icon-search",
|
||||||
isActive: true
|
isActive: true,
|
||||||
}
|
};
|
||||||
|
|
||||||
this._focusPending = true;
|
this._focusPending = true;
|
||||||
//below is hacking, improved when we have proper routing
|
//below is hacking, improved when we have proper routing
|
||||||
if (!this.state.address.startsWith('lbry://') && this.state.icon !== "icon-search") //onFocus, if they are not on an exact URL or a search page, clear the bar
|
if (
|
||||||
{
|
!this.state.address.startsWith("lbry://") &&
|
||||||
newState.address = '';
|
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.setState(newState);
|
||||||
}
|
}
|
||||||
|
@ -83,14 +87,13 @@ class WunderBar extends React.PureComponent {
|
||||||
if (this._isSearchDispatchPending) {
|
if (this._isSearchDispatchPending) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.onBlur();
|
this.onBlur();
|
||||||
}, WunderBar.TYPING_TIMEOUT + 1)
|
}, WunderBar.TYPING_TIMEOUT + 1);
|
||||||
} else {
|
} else {
|
||||||
let commonState = {isActive: false};
|
let commonState = { isActive: false };
|
||||||
if (this._resetOnNextBlur) {
|
if (this._resetOnNextBlur) {
|
||||||
this.setState(Object.assign({}, this._stateBeforeSearch, commonState));
|
this.setState(Object.assign({}, this._stateBeforeSearch, commonState));
|
||||||
this._input.value = this.state.address;
|
this._input.value = this.state.address;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this._resetOnNextBlur = true;
|
this._resetOnNextBlur = true;
|
||||||
this._stateBeforeSearch = this.state;
|
this._stateBeforeSearch = this.state;
|
||||||
this.setState(commonState);
|
this.setState(commonState);
|
||||||
|
@ -116,9 +119,8 @@ class WunderBar extends React.PureComponent {
|
||||||
|
|
||||||
onKeyPress(event) {
|
onKeyPress(event) {
|
||||||
if (event.charCode == 13 && this._input.value) {
|
if (event.charCode == 13 && this._input.value) {
|
||||||
|
|
||||||
let uri = null,
|
let uri = null,
|
||||||
method = "onSubmit";
|
method = "onSubmit";
|
||||||
|
|
||||||
this._resetOnNextBlur = false;
|
this._resetOnNextBlur = false;
|
||||||
clearTimeout(this._userTypingTimer);
|
clearTimeout(this._userTypingTimer);
|
||||||
|
@ -126,7 +128,8 @@ class WunderBar extends React.PureComponent {
|
||||||
try {
|
try {
|
||||||
uri = lbryuri.normalize(this._input.value);
|
uri = lbryuri.normalize(this._input.value);
|
||||||
this.setState({ value: uri });
|
this.setState({ value: uri });
|
||||||
} catch (error) { //then it's not a valid URL, so let's search
|
} catch (error) {
|
||||||
|
//then it's not a valid URL, so let's search
|
||||||
uri = this._input.value;
|
uri = this._input.value;
|
||||||
method = "onSearch";
|
method = "onSearch";
|
||||||
}
|
}
|
||||||
|
@ -142,16 +145,23 @@ class WunderBar extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className={'wunderbar' + (this.state.isActive ? ' wunderbar--active' : '')}>
|
<div
|
||||||
{this.state.icon ? <Icon fixed icon={this.state.icon} /> : '' }
|
className={
|
||||||
<input className="wunderbar__input" type="search"
|
"wunderbar" + (this.state.isActive ? " wunderbar--active" : "")
|
||||||
ref={this.onReceiveRef}
|
}
|
||||||
onFocus={this.onFocus}
|
>
|
||||||
onBlur={this.onBlur}
|
{this.state.icon ? <Icon fixed icon={this.state.icon} /> : ""}
|
||||||
onChange={this.onChange}
|
<input
|
||||||
onKeyPress={this.onKeyPress}
|
className="wunderbar__input"
|
||||||
value={this.state.address}
|
type="search"
|
||||||
placeholder="Find movies, music, games, and more" />
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
module.exports = {
|
module.exports = {};
|
||||||
}
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
module.exports = {
|
module.exports = {};
|
||||||
}
|
|
||||||
|
|
|
@ -1,68 +1,95 @@
|
||||||
export const CHANGE_PATH = 'CHANGE_PATH'
|
export const CHANGE_PATH = "CHANGE_PATH";
|
||||||
export const OPEN_MODAL = 'OPEN_MODAL'
|
export const OPEN_MODAL = "OPEN_MODAL";
|
||||||
export const CLOSE_MODAL = 'CLOSE_MODAL'
|
export const CLOSE_MODAL = "CLOSE_MODAL";
|
||||||
export const HISTORY_BACK = 'HISTORY_BACK'
|
export const HISTORY_BACK = "HISTORY_BACK";
|
||||||
export const SHOW_SNACKBAR = 'SHOW_SNACKBAR'
|
export const SHOW_SNACKBAR = "SHOW_SNACKBAR";
|
||||||
export const REMOVE_SNACKBAR_SNACK = 'REMOVE_SNACKBAR_SNACK'
|
export const REMOVE_SNACKBAR_SNACK = "REMOVE_SNACKBAR_SNACK";
|
||||||
|
|
||||||
export const DAEMON_READY = 'DAEMON_READY'
|
export const DAEMON_READY = "DAEMON_READY";
|
||||||
|
|
||||||
// Upgrades
|
// Upgrades
|
||||||
export const UPGRADE_CANCELLED = 'UPGRADE_CANCELLED'
|
export const UPGRADE_CANCELLED = "UPGRADE_CANCELLED";
|
||||||
export const DOWNLOAD_UPGRADE = 'DOWNLOAD_UPGRADE'
|
export const DOWNLOAD_UPGRADE = "DOWNLOAD_UPGRADE";
|
||||||
export const UPGRADE_DOWNLOAD_STARTED = 'UPGRADE_DOWNLOAD_STARTED'
|
export const UPGRADE_DOWNLOAD_STARTED = "UPGRADE_DOWNLOAD_STARTED";
|
||||||
export const UPGRADE_DOWNLOAD_COMPLETED = 'UPGRADE_DOWNLOAD_COMPLETED'
|
export const UPGRADE_DOWNLOAD_COMPLETED = "UPGRADE_DOWNLOAD_COMPLETED";
|
||||||
export const UPGRADE_DOWNLOAD_PROGRESSED = 'UPGRADE_DOWNLOAD_PROGRESSED'
|
export const UPGRADE_DOWNLOAD_PROGRESSED = "UPGRADE_DOWNLOAD_PROGRESSED";
|
||||||
export const CHECK_UPGRADE_AVAILABLE = 'CHECK_UPGRADE_AVAILABLE'
|
export const CHECK_UPGRADE_AVAILABLE = "CHECK_UPGRADE_AVAILABLE";
|
||||||
export const UPDATE_VERSION = 'UPDATE_VERSION'
|
export const UPDATE_VERSION = "UPDATE_VERSION";
|
||||||
export const SKIP_UPGRADE = 'SKIP_UPGRADE'
|
export const SKIP_UPGRADE = "SKIP_UPGRADE";
|
||||||
export const START_UPGRADE = 'START_UPGRADE'
|
export const START_UPGRADE = "START_UPGRADE";
|
||||||
|
|
||||||
// Wallet
|
// Wallet
|
||||||
export const GET_NEW_ADDRESS_STARTED = 'GET_NEW_ADDRESS_STARTED'
|
export const GET_NEW_ADDRESS_STARTED = "GET_NEW_ADDRESS_STARTED";
|
||||||
export const GET_NEW_ADDRESS_COMPLETED = 'GET_NEW_ADDRESS_COMPLETED'
|
export const GET_NEW_ADDRESS_COMPLETED = "GET_NEW_ADDRESS_COMPLETED";
|
||||||
export const FETCH_TRANSACTIONS_STARTED = 'FETCH_TRANSACTIONS_STARTED'
|
export const FETCH_TRANSACTIONS_STARTED = "FETCH_TRANSACTIONS_STARTED";
|
||||||
export const FETCH_TRANSACTIONS_COMPLETED = 'FETCH_TRANSACTIONS_COMPLETED'
|
export const FETCH_TRANSACTIONS_COMPLETED = "FETCH_TRANSACTIONS_COMPLETED";
|
||||||
export const UPDATE_BALANCE = 'UPDATE_BALANCE'
|
export const UPDATE_BALANCE = "UPDATE_BALANCE";
|
||||||
export const CHECK_ADDRESS_IS_MINE_STARTED = 'CHECK_ADDRESS_IS_MINE_STARTED'
|
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 CHECK_ADDRESS_IS_MINE_COMPLETED =
|
||||||
export const SET_DRAFT_TRANSACTION_AMOUNT = 'SET_DRAFT_TRANSACTION_AMOUNT'
|
"CHECK_ADDRESS_IS_MINE_COMPLETED";
|
||||||
export const SET_DRAFT_TRANSACTION_ADDRESS = 'SET_DRAFT_TRANSACTION_ADDRESS'
|
export const SET_DRAFT_TRANSACTION_AMOUNT = "SET_DRAFT_TRANSACTION_AMOUNT";
|
||||||
export const SEND_TRANSACTION_STARTED = 'SEND_TRANSACTION_STARTED'
|
export const SET_DRAFT_TRANSACTION_ADDRESS = "SET_DRAFT_TRANSACTION_ADDRESS";
|
||||||
export const SEND_TRANSACTION_COMPLETED = 'SEND_TRANSACTION_COMPLETED'
|
export const SEND_TRANSACTION_STARTED = "SEND_TRANSACTION_STARTED";
|
||||||
export const SEND_TRANSACTION_FAILED = 'SEND_TRANSACTION_FAILED'
|
export const SEND_TRANSACTION_COMPLETED = "SEND_TRANSACTION_COMPLETED";
|
||||||
|
export const SEND_TRANSACTION_FAILED = "SEND_TRANSACTION_FAILED";
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
export const FETCH_FEATURED_CONTENT_STARTED = 'FETCH_FEATURED_CONTENT_STARTED'
|
export const FETCH_FEATURED_CONTENT_STARTED = "FETCH_FEATURED_CONTENT_STARTED";
|
||||||
export const FETCH_FEATURED_CONTENT_COMPLETED = 'FETCH_FEATURED_CONTENT_COMPLETED'
|
export const FETCH_FEATURED_CONTENT_COMPLETED =
|
||||||
export const RESOLVE_URI_STARTED = 'RESOLVE_URI_STARTED'
|
"FETCH_FEATURED_CONTENT_COMPLETED";
|
||||||
export const RESOLVE_URI_COMPLETED = 'RESOLVE_URI_COMPLETED'
|
export const RESOLVE_URI_STARTED = "RESOLVE_URI_STARTED";
|
||||||
export const RESOLVE_URI_CANCELED = 'RESOLVE_URI_CANCELED'
|
export const RESOLVE_URI_COMPLETED = "RESOLVE_URI_COMPLETED";
|
||||||
export const FETCH_CHANNEL_CLAIMS_STARTED = 'FETCH_CHANNEL_CLAIMS_STARTED'
|
export const RESOLVE_URI_CANCELED = "RESOLVE_URI_CANCELED";
|
||||||
export const FETCH_CHANNEL_CLAIMS_COMPLETED = 'FETCH_CHANNEL_CLAIMS_COMPLETED'
|
export const FETCH_CHANNEL_CLAIMS_STARTED = "FETCH_CHANNEL_CLAIMS_STARTED";
|
||||||
export const FETCH_CLAIM_LIST_MINE_STARTED = 'FETCH_CLAIM_LIST_MINE_STARTED'
|
export const FETCH_CHANNEL_CLAIMS_COMPLETED = "FETCH_CHANNEL_CLAIMS_COMPLETED";
|
||||||
export const FETCH_CLAIM_LIST_MINE_COMPLETED = 'FETCH_CLAIM_LIST_MINE_COMPLETED'
|
export const FETCH_CLAIM_LIST_MINE_STARTED = "FETCH_CLAIM_LIST_MINE_STARTED";
|
||||||
export const FILE_LIST_STARTED = 'FILE_LIST_STARTED'
|
export const FETCH_CLAIM_LIST_MINE_COMPLETED =
|
||||||
export const FILE_LIST_COMPLETED = 'FILE_LIST_COMPLETED'
|
"FETCH_CLAIM_LIST_MINE_COMPLETED";
|
||||||
export const FETCH_FILE_INFO_STARTED = 'FETCH_FILE_INFO_STARTED'
|
export const FILE_LIST_STARTED = "FILE_LIST_STARTED";
|
||||||
export const FETCH_FILE_INFO_COMPLETED = 'FETCH_FILE_INFO_COMPLETED'
|
export const FILE_LIST_COMPLETED = "FILE_LIST_COMPLETED";
|
||||||
export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED'
|
export const FETCH_FILE_INFO_STARTED = "FETCH_FILE_INFO_STARTED";
|
||||||
export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED'
|
export const FETCH_FILE_INFO_COMPLETED = "FETCH_FILE_INFO_COMPLETED";
|
||||||
export const LOADING_VIDEO_STARTED = 'LOADING_VIDEO_STARTED'
|
export const FETCH_COST_INFO_STARTED = "FETCH_COST_INFO_STARTED";
|
||||||
export const LOADING_VIDEO_COMPLETED = 'LOADING_VIDEO_COMPLETED'
|
export const FETCH_COST_INFO_COMPLETED = "FETCH_COST_INFO_COMPLETED";
|
||||||
export const LOADING_VIDEO_FAILED = 'LOADING_VIDEO_FAILED'
|
export const LOADING_VIDEO_STARTED = "LOADING_VIDEO_STARTED";
|
||||||
export const DOWNLOADING_STARTED = 'DOWNLOADING_STARTED'
|
export const LOADING_VIDEO_COMPLETED = "LOADING_VIDEO_COMPLETED";
|
||||||
export const DOWNLOADING_PROGRESSED = 'DOWNLOADING_PROGRESSED'
|
export const LOADING_VIDEO_FAILED = "LOADING_VIDEO_FAILED";
|
||||||
export const DOWNLOADING_COMPLETED = 'DOWNLOADING_COMPLETED'
|
export const DOWNLOADING_STARTED = "DOWNLOADING_STARTED";
|
||||||
export const PLAY_VIDEO_STARTED = 'PLAY_VIDEO_STARTED'
|
export const DOWNLOADING_PROGRESSED = "DOWNLOADING_PROGRESSED";
|
||||||
export const FETCH_AVAILABILITY_STARTED = 'FETCH_AVAILABILITY_STARTED'
|
export const DOWNLOADING_COMPLETED = "DOWNLOADING_COMPLETED";
|
||||||
export const FETCH_AVAILABILITY_COMPLETED = 'FETCH_AVAILABILITY_COMPLETED'
|
export const PLAY_VIDEO_STARTED = "PLAY_VIDEO_STARTED";
|
||||||
export const FILE_DELETE = 'FILE_DELETE'
|
export const FETCH_AVAILABILITY_STARTED = "FETCH_AVAILABILITY_STARTED";
|
||||||
|
export const FETCH_AVAILABILITY_COMPLETED = "FETCH_AVAILABILITY_COMPLETED";
|
||||||
|
export const FILE_DELETE = "FILE_DELETE";
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
export const SEARCH_STARTED = 'SEARCH_STARTED'
|
export const SEARCH_STARTED = "SEARCH_STARTED";
|
||||||
export const SEARCH_COMPLETED = 'SEARCH_COMPLETED'
|
export const SEARCH_COMPLETED = "SEARCH_COMPLETED";
|
||||||
export const SEARCH_CANCELLED = 'SEARCH_CANCELLED'
|
export const SEARCH_CANCELLED = "SEARCH_CANCELLED";
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
export const DAEMON_SETTINGS_RECEIVED = 'DAEMON_SETTINGS_RECEIVED'
|
export const DAEMON_SETTINGS_RECEIVED = "DAEMON_SETTINGS_RECEIVED";
|
||||||
|
|
||||||
|
// User
|
||||||
|
export const AUTHENTICATION_STARTED = "AUTHENTICATION_STARTED";
|
||||||
|
export const AUTHENTICATION_SUCCESS = "AUTHENTICATION_SUCCESS";
|
||||||
|
export const AUTHENTICATION_FAILURE = "AUTHENTICATION_FAILURE";
|
||||||
|
export const USER_EMAIL_DECLINE = "USER_EMAIL_DECLINE";
|
||||||
|
export const USER_EMAIL_NEW_STARTED = "USER_EMAIL_NEW_STARTED";
|
||||||
|
export const USER_EMAIL_NEW_SUCCESS = "USER_EMAIL_NEW_SUCCESS";
|
||||||
|
export const USER_EMAIL_NEW_EXISTS = "USER_EMAIL_NEW_EXISTS";
|
||||||
|
export const USER_EMAIL_NEW_FAILURE = "USER_EMAIL_NEW_FAILURE";
|
||||||
|
export const USER_EMAIL_VERIFY_STARTED = "USER_EMAIL_VERIFY_STARTED";
|
||||||
|
export const USER_EMAIL_VERIFY_SUCCESS = "USER_EMAIL_VERIFY_SUCCESS";
|
||||||
|
export const USER_EMAIL_VERIFY_FAILURE = "USER_EMAIL_VERIFY_FAILURE";
|
||||||
|
export const USER_FETCH_STARTED = "USER_FETCH_STARTED";
|
||||||
|
export const USER_FETCH_SUCCESS = "USER_FETCH_SUCCESS";
|
||||||
|
export const USER_FETCH_FAILURE = "USER_FETCH_FAILURE";
|
||||||
|
|
||||||
|
// Rewards
|
||||||
|
export const FETCH_REWARDS_STARTED = "FETCH_REWARDS_STARTED";
|
||||||
|
export const FETCH_REWARDS_COMPLETED = "FETCH_REWARDS_COMPLETED";
|
||||||
|
export const CLAIM_REWARD_STARTED = "CLAIM_REWARD_STARTED";
|
||||||
|
export const CLAIM_REWARD_SUCCESS = "CLAIM_REWARD_SUCCESS";
|
||||||
|
export const CLAIM_REWARD_FAILURE = "CLAIM_REWARD_FAILURE";
|
||||||
|
export const CLAIM_REWARD_CLEAR_ERROR = "CLAIM_REWARD_CLEAR_ERROR";
|
||||||
|
|
1
ui/js/constants/modal_types.js
Normal file
1
ui/js/constants/modal_types.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const WELCOME = "welcome";
|
138
ui/js/jsonrpc.js
138
ui/js/jsonrpc.js
|
@ -1,75 +1,87 @@
|
||||||
const jsonrpc = {};
|
const jsonrpc = {};
|
||||||
|
|
||||||
jsonrpc.call = function (connectionString, method, params, callback, errorCallback, connectFailedCallback, timeout) {
|
jsonrpc.call = function(
|
||||||
var xhr = new XMLHttpRequest;
|
connectionString,
|
||||||
if (typeof connectFailedCallback !== 'undefined') {
|
method,
|
||||||
if (timeout) {
|
params,
|
||||||
xhr.timeout = timeout;
|
callback,
|
||||||
}
|
errorCallback,
|
||||||
|
connectFailedCallback,
|
||||||
|
timeout
|
||||||
|
) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
if (typeof connectFailedCallback !== 'undefined') {
|
||||||
|
if (timeout) {
|
||||||
|
xhr.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
xhr.addEventListener('error', function (e) {
|
xhr.addEventListener('error', function(e) {
|
||||||
connectFailedCallback(e);
|
connectFailedCallback(e);
|
||||||
});
|
});
|
||||||
xhr.addEventListener('timeout', function() {
|
xhr.addEventListener('timeout', function() {
|
||||||
connectFailedCallback(new Error('XMLHttpRequest connection timed out'));
|
connectFailedCallback(
|
||||||
})
|
new Error(__('XMLHttpRequest connection timed out'))
|
||||||
}
|
);
|
||||||
xhr.addEventListener('load', function() {
|
});
|
||||||
var response = JSON.parse(xhr.responseText);
|
}
|
||||||
|
xhr.addEventListener('load', function() {
|
||||||
|
var response = JSON.parse(xhr.responseText);
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
if (errorCallback) {
|
if (errorCallback) {
|
||||||
errorCallback(response.error);
|
errorCallback(response.error);
|
||||||
} else {
|
} else {
|
||||||
var errorEvent = new CustomEvent('unhandledError', {
|
var errorEvent = new CustomEvent('unhandledError', {
|
||||||
detail: {
|
detail: {
|
||||||
connectionString: connectionString,
|
connectionString: connectionString,
|
||||||
method: method,
|
method: method,
|
||||||
params: params,
|
params: params,
|
||||||
code: response.error.code,
|
code: response.error.code,
|
||||||
message: response.error.message,
|
message: response.error.message,
|
||||||
data: response.error.data
|
data: response.error.data
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.dispatchEvent(errorEvent)
|
document.dispatchEvent(errorEvent);
|
||||||
}
|
}
|
||||||
} else if (callback) {
|
} else if (callback) {
|
||||||
callback(response.result);
|
callback(response.result);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (connectFailedCallback) {
|
if (connectFailedCallback) {
|
||||||
xhr.addEventListener('error', function (event) {
|
xhr.addEventListener('error', function(event) {
|
||||||
connectFailedCallback(event);
|
connectFailedCallback(event);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
xhr.addEventListener('error', function (event) {
|
xhr.addEventListener('error', function(event) {
|
||||||
var errorEvent = new CustomEvent('unhandledError', {
|
var errorEvent = new CustomEvent('unhandledError', {
|
||||||
detail: {
|
detail: {
|
||||||
connectionString: connectionString,
|
connectionString: connectionString,
|
||||||
method: method,
|
method: method,
|
||||||
params: params,
|
params: params,
|
||||||
code: xhr.status,
|
code: xhr.status,
|
||||||
message: 'Connection to API server failed'
|
message: __('Connection to API server failed')
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.dispatchEvent(errorEvent);
|
document.dispatchEvent(errorEvent);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const counter = parseInt(sessionStorage.getItem('JSONRPCCounter') || 0);
|
const counter = parseInt(sessionStorage.getItem('JSONRPCCounter') || 0);
|
||||||
|
|
||||||
xhr.open('POST', connectionString, true);
|
xhr.open('POST', connectionString, true);
|
||||||
xhr.send(JSON.stringify({
|
xhr.send(
|
||||||
'jsonrpc': '2.0',
|
JSON.stringify({
|
||||||
'method': method,
|
jsonrpc: '2.0',
|
||||||
'params': params,
|
method: method,
|
||||||
'id': counter,
|
params: params,
|
||||||
}));
|
id: counter
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
sessionStorage.setItem('JSONRPCCounter', counter + 1);
|
sessionStorage.setItem('JSONRPCCounter', counter + 1);
|
||||||
|
|
||||||
return xhr
|
return xhr;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default jsonrpc;
|
export default jsonrpc;
|
||||||
|
|
717
ui/js/lbry.js
717
ui/js/lbry.js
|
@ -2,59 +2,68 @@ import lbryio from './lbryio.js';
|
||||||
import lighthouse from './lighthouse.js';
|
import lighthouse from './lighthouse.js';
|
||||||
import jsonrpc from './jsonrpc.js';
|
import jsonrpc from './jsonrpc.js';
|
||||||
import lbryuri from './lbryuri.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');
|
const menu = remote.require('./menu/main-menu');
|
||||||
|
|
||||||
let lbry = {
|
let lbry = {
|
||||||
isConnected: false,
|
isConnected: false,
|
||||||
daemonConnectionString: 'http://localhost:5279/lbryapi',
|
daemonConnectionString: 'http://localhost:5279/lbryapi',
|
||||||
pendingPublishTimeout: 20 * 60 * 1000,
|
pendingPublishTimeout: 20 * 60 * 1000,
|
||||||
defaultClientSettings: {
|
defaultClientSettings: {
|
||||||
showNsfw: false,
|
showNsfw: false,
|
||||||
showUnavailable: true,
|
showUnavailable: true,
|
||||||
debug: false,
|
debug: false,
|
||||||
useCustomLighthouseServers: false,
|
useCustomLighthouseServers: false,
|
||||||
customLighthouseServers: [],
|
customLighthouseServers: [],
|
||||||
showDeveloperMenu: false,
|
showDeveloperMenu: false,
|
||||||
}
|
language: 'en'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records a publish attempt in local storage. Returns a dictionary with all the data needed to
|
* 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.
|
* needed to make a dummy claim or file info object.
|
||||||
*/
|
*/
|
||||||
function savePendingPublish({name, channel_name}) {
|
function savePendingPublish({ name, channel_name }) {
|
||||||
let uri;
|
let uri;
|
||||||
if (channel_name) {
|
if (channel_name) {
|
||||||
uri = lbryuri.build({name: channel_name, path: name}, false);
|
uri = lbryuri.build({ name: channel_name, path: name }, false);
|
||||||
} else {
|
} else {
|
||||||
uri = lbryuri.build({name: name}, false);
|
uri = lbryuri.build({ name: name }, false);
|
||||||
}
|
}
|
||||||
const pendingPublishes = getLocal('pendingPublishes') || [];
|
const pendingPublishes = getLocal('pendingPublishes') || [];
|
||||||
const newPendingPublish = {
|
const newPendingPublish = {
|
||||||
name, channel_name,
|
name,
|
||||||
claim_id: 'pending_claim_' + uri,
|
channel_name,
|
||||||
txid: 'pending_' + uri,
|
claim_id: 'pending_claim_' + uri,
|
||||||
nout: 0,
|
txid: 'pending_' + uri,
|
||||||
outpoint: 'pending_' + uri + ':0',
|
nout: 0,
|
||||||
time: Date.now(),
|
outpoint: 'pending_' + uri + ':0',
|
||||||
};
|
time: Date.now()
|
||||||
setLocal('pendingPublishes', [...pendingPublishes, newPendingPublish]);
|
};
|
||||||
return newPendingPublish;
|
setLocal('pendingPublishes', [...pendingPublishes, newPendingPublish]);
|
||||||
|
return newPendingPublish;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there is a pending publish with the given name or outpoint, remove it.
|
* If there is a pending publish with the given name or outpoint, remove it.
|
||||||
* A channel name may also be provided along with name.
|
* A channel name may also be provided along with name.
|
||||||
*/
|
*/
|
||||||
function removePendingPublishIfNeeded({name, channel_name, outpoint}) {
|
function removePendingPublishIfNeeded({ name, channel_name, outpoint }) {
|
||||||
function pubMatches(pub) {
|
function pubMatches(pub) {
|
||||||
return pub.outpoint === outpoint || (pub.name === name && (!channel_name || pub.channel_name === channel_name));
|
return (
|
||||||
}
|
pub.outpoint === outpoint ||
|
||||||
|
(pub.name === name &&
|
||||||
|
(!channel_name || pub.channel_name === channel_name))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
setLocal('pendingPublishes', lbry.getPendingPublishes().filter(pub => !pubMatches(pub)));
|
setLocal(
|
||||||
|
'pendingPublishes',
|
||||||
|
lbry.getPendingPublishes().filter(pub => !pubMatches(pub))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,74 +71,111 @@ function removePendingPublishIfNeeded({name, channel_name, outpoint}) {
|
||||||
* removes them from the list.
|
* removes them from the list.
|
||||||
*/
|
*/
|
||||||
lbry.getPendingPublishes = function() {
|
lbry.getPendingPublishes = function() {
|
||||||
const pendingPublishes = getLocal('pendingPublishes') || [];
|
const pendingPublishes = getLocal('pendingPublishes') || [];
|
||||||
const newPendingPublishes = pendingPublishes.filter(pub => Date.now() - pub.time <= lbry.pendingPublishTimeout);
|
const newPendingPublishes = pendingPublishes.filter(
|
||||||
setLocal('pendingPublishes', newPendingPublishes);
|
pub => Date.now() - pub.time <= lbry.pendingPublishTimeout
|
||||||
return newPendingPublishes;
|
);
|
||||||
}
|
setLocal('pendingPublishes', newPendingPublishes);
|
||||||
|
return newPendingPublishes;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a pending publish attempt by its name or (fake) outpoint. A channel name can also be
|
* 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.
|
* provided along withe the name. If no pending publish is found, returns null.
|
||||||
*/
|
*/
|
||||||
function getPendingPublish({name, channel_name, outpoint}) {
|
function getPendingPublish({ name, channel_name, outpoint }) {
|
||||||
const pendingPublishes = lbry.getPendingPublishes();
|
const pendingPublishes = lbry.getPendingPublishes();
|
||||||
return pendingPublishes.find(
|
return (
|
||||||
pub => pub.outpoint === outpoint || (pub.name === name && (!channel_name || pub.channel_name === channel_name))
|
pendingPublishes.find(
|
||||||
) || null;
|
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}) {
|
function pendingPublishToDummyClaim({
|
||||||
return {name, outpoint, claim_id, txid, nout, channel_name};
|
channel_name,
|
||||||
|
name,
|
||||||
|
outpoint,
|
||||||
|
claim_id,
|
||||||
|
txid,
|
||||||
|
nout
|
||||||
|
}) {
|
||||||
|
return { name, outpoint, claim_id, txid, nout, channel_name };
|
||||||
}
|
}
|
||||||
|
|
||||||
function pendingPublishToDummyFileInfo({name, outpoint, claim_id}) {
|
function pendingPublishToDummyFileInfo({ name, outpoint, claim_id }) {
|
||||||
return {name, outpoint, claim_id, metadata: null};
|
return { name, outpoint, claim_id, metadata: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
lbry.call = function (method, params, callback, errorCallback, connectFailedCallback) {
|
lbry.call = function(
|
||||||
return jsonrpc.call(lbry.daemonConnectionString, method, params, callback, errorCallback, connectFailedCallback);
|
method,
|
||||||
}
|
params,
|
||||||
|
callback,
|
||||||
|
errorCallback,
|
||||||
|
connectFailedCallback
|
||||||
|
) {
|
||||||
|
return jsonrpc.call(
|
||||||
|
lbry.daemonConnectionString,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
callback,
|
||||||
|
errorCallback,
|
||||||
|
connectFailedCallback
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
//core
|
//core
|
||||||
lbry._connectPromise = null;
|
lbry._connectPromise = null;
|
||||||
lbry.connect = function() {
|
lbry.connect = function() {
|
||||||
if (lbry._connectPromise === null) {
|
if (lbry._connectPromise === null) {
|
||||||
lbry._connectPromise = new Promise((resolve, reject) => {
|
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() {
|
// Check every half second to see if the daemon is accepting connections
|
||||||
if (tryNum <= 100) { // Move # of tries into constant or config option
|
function checkDaemonStarted() {
|
||||||
setTimeout(() => {
|
lbry.call(
|
||||||
tryNum++
|
'status',
|
||||||
checkDaemonStarted();
|
{},
|
||||||
}, tryNum < 50 ? 400 : 1000);
|
resolve,
|
||||||
}
|
checkDaemonStartedFailed,
|
||||||
else {
|
checkDaemonStartedFailed
|
||||||
reject(new Error("Unable to connect to LBRY"));
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Check every half second to see if the daemon is accepting connections
|
checkDaemonStarted();
|
||||||
function checkDaemonStarted() {
|
});
|
||||||
lbry.call('status', {}, resolve, checkDaemonStartedFailed, checkDaemonStartedFailed)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
checkDaemonStarted();
|
return lbry._connectPromise;
|
||||||
});
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return lbry._connectPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
lbry.checkAddressIsMine = function(address, callback) {
|
lbry.checkAddressIsMine = function(address, callback) {
|
||||||
lbry.call('address_is_mine', {address: address}, callback);
|
lbry.call('wallet_is_address_mine', { address: address }, callback);
|
||||||
}
|
};
|
||||||
|
|
||||||
lbry.sendToAddress = function(amount, address, callback, errorCallback) {
|
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
|
* Takes a LBRY URI; will first try and calculate a total cost using
|
||||||
|
@ -141,48 +187,49 @@ lbry.sendToAddress = function(amount, address, callback, errorCallback) {
|
||||||
* - includes_data: Boolean; indicates whether or not the data fee info
|
* - includes_data: Boolean; indicates whether or not the data fee info
|
||||||
* from Lighthouse is included.
|
* from Lighthouse is included.
|
||||||
*/
|
*/
|
||||||
lbry.costPromiseCache = {}
|
lbry.costPromiseCache = {};
|
||||||
lbry.getCostInfo = function(uri) {
|
lbry.getCostInfo = function(uri) {
|
||||||
if (lbry.costPromiseCache[uri] === undefined) {
|
if (lbry.costPromiseCache[uri] === undefined) {
|
||||||
lbry.costPromiseCache[uri] = new Promise((resolve, reject) => {
|
lbry.costPromiseCache[uri] = new Promise((resolve, reject) => {
|
||||||
const COST_INFO_CACHE_KEY = 'cost_info_cache';
|
const COST_INFO_CACHE_KEY = 'cost_info_cache';
|
||||||
let costInfoCache = getSession(COST_INFO_CACHE_KEY, {})
|
let costInfoCache = getSession(COST_INFO_CACHE_KEY, {});
|
||||||
|
|
||||||
function cacheAndResolve(cost, includesData) {
|
function cacheAndResolve(cost, includesData) {
|
||||||
costInfoCache[uri] = {cost, includesData};
|
costInfoCache[uri] = { cost, includesData };
|
||||||
setSession(COST_INFO_CACHE_KEY, costInfoCache);
|
setSession(COST_INFO_CACHE_KEY, costInfoCache);
|
||||||
resolve({cost, includesData});
|
resolve({ cost, includesData });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!uri) {
|
if (!uri) {
|
||||||
return reject(new Error(`URI required.`));
|
return reject(new Error(`URI required.`));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (costInfoCache[uri] && costInfoCache[uri].cost) {
|
if (costInfoCache[uri] && costInfoCache[uri].cost) {
|
||||||
return resolve(costInfoCache[uri])
|
return resolve(costInfoCache[uri]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCost(uri, size) {
|
function getCost(uri, size) {
|
||||||
lbry.stream_cost_estimate({uri, ... size !== null ? {size} : {}}).then((cost) => {
|
lbry
|
||||||
cacheAndResolve(cost, size !== null);
|
.stream_cost_estimate({ uri, ...(size !== null ? { size } : {}) })
|
||||||
}, reject);
|
.then(cost => {
|
||||||
}
|
cacheAndResolve(cost, size !== null);
|
||||||
|
}, reject);
|
||||||
|
}
|
||||||
|
|
||||||
const uriObj = lbryuri.parse(uri);
|
const uriObj = lbryuri.parse(uri);
|
||||||
const name = uriObj.path || uriObj.name;
|
const name = uriObj.path || uriObj.name;
|
||||||
|
|
||||||
lighthouse.get_size_for_name(name).then((size) => {
|
lighthouse.get_size_for_name(name).then(size => {
|
||||||
if (size) {
|
if (size) {
|
||||||
getCost(name, size);
|
getCost(name, size);
|
||||||
}
|
} else {
|
||||||
else {
|
getCost(name, null);
|
||||||
getCost(name, null);
|
}
|
||||||
}
|
});
|
||||||
})
|
});
|
||||||
});
|
}
|
||||||
}
|
return lbry.costPromiseCache[uri];
|
||||||
return lbry.costPromiseCache[uri];
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publishes a file. The optional fileListedCallback is called when the file becomes available in
|
* Publishes a file. The optional fileListedCallback is called when the file becomes available in
|
||||||
|
@ -191,125 +238,144 @@ lbry.getCostInfo = function(uri) {
|
||||||
* This currently includes a work-around to cache the file in local storage so that the pending
|
* This currently includes a work-around to cache the file in local storage so that the pending
|
||||||
* publish can appear in the UI immediately.
|
* publish can appear in the UI immediately.
|
||||||
*/
|
*/
|
||||||
lbry.publish = function(params, fileListedCallback, publishedCallback, errorCallback) {
|
lbry.publish = function(
|
||||||
lbry.call('publish', params, (result) => {
|
params,
|
||||||
if (returnedPending) {
|
fileListedCallback,
|
||||||
return;
|
publishedCallback,
|
||||||
}
|
errorCallback
|
||||||
|
) {
|
||||||
|
lbry.call(
|
||||||
|
'publish',
|
||||||
|
params,
|
||||||
|
result => {
|
||||||
|
if (returnedPending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
clearTimeout(returnPendingTimeout);
|
clearTimeout(returnPendingTimeout);
|
||||||
publishedCallback(result);
|
publishedCallback(result);
|
||||||
}, (err) => {
|
},
|
||||||
if (returnedPending) {
|
err => {
|
||||||
return;
|
if (returnedPending) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
clearTimeout(returnPendingTimeout);
|
clearTimeout(returnPendingTimeout);
|
||||||
errorCallback(err);
|
errorCallback(err);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
let returnedPending = false;
|
let returnedPending = false;
|
||||||
// Give a short grace period in case publish() returns right away or (more likely) gives an error
|
// Give a short grace period in case publish() returns right away or (more likely) gives an error
|
||||||
const returnPendingTimeout = setTimeout(() => {
|
const returnPendingTimeout = setTimeout(() => {
|
||||||
returnedPending = true;
|
returnedPending = true;
|
||||||
|
|
||||||
if (publishedCallback) {
|
if (publishedCallback) {
|
||||||
savePendingPublish({name: params.name, channel_name: params.channel_name});
|
savePendingPublish({
|
||||||
publishedCallback(true);
|
name: params.name,
|
||||||
}
|
channel_name: params.channel_name
|
||||||
|
});
|
||||||
if (fileListedCallback) {
|
publishedCallback(true);
|
||||||
const {name, channel_name} = params;
|
}
|
||||||
savePendingPublish({name: params.name, channel_name: params.channel_name});
|
|
||||||
fileListedCallback(true);
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (fileListedCallback) {
|
||||||
|
const { name, channel_name } = params;
|
||||||
|
savePendingPublish({
|
||||||
|
name: params.name,
|
||||||
|
channel_name: params.channel_name
|
||||||
|
});
|
||||||
|
fileListedCallback(true);
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
lbry.getClientSettings = function() {
|
lbry.getClientSettings = function() {
|
||||||
var outSettings = {};
|
var outSettings = {};
|
||||||
for (let setting of Object.keys(lbry.defaultClientSettings)) {
|
for (let setting of Object.keys(lbry.defaultClientSettings)) {
|
||||||
var localStorageVal = localStorage.getItem('setting_' + setting);
|
var localStorageVal = localStorage.getItem('setting_' + setting);
|
||||||
outSettings[setting] = (localStorageVal === null ? lbry.defaultClientSettings[setting] : JSON.parse(localStorageVal));
|
outSettings[setting] = localStorageVal === null
|
||||||
}
|
? lbry.defaultClientSettings[setting]
|
||||||
return outSettings;
|
: JSON.parse(localStorageVal);
|
||||||
}
|
}
|
||||||
|
return outSettings;
|
||||||
|
};
|
||||||
|
|
||||||
lbry.getClientSetting = function(setting) {
|
lbry.getClientSetting = function(setting) {
|
||||||
var localStorageVal = localStorage.getItem('setting_' + setting);
|
var localStorageVal = localStorage.getItem('setting_' + setting);
|
||||||
if (setting == 'showDeveloperMenu')
|
if (setting == 'showDeveloperMenu') {
|
||||||
{
|
return true;
|
||||||
return true;
|
}
|
||||||
}
|
return localStorageVal === null
|
||||||
return (localStorageVal === null ? lbry.defaultClientSettings[setting] : JSON.parse(localStorageVal));
|
? lbry.defaultClientSettings[setting]
|
||||||
}
|
: JSON.parse(localStorageVal);
|
||||||
|
};
|
||||||
|
|
||||||
lbry.setClientSettings = function(settings) {
|
lbry.setClientSettings = function(settings) {
|
||||||
for (let setting of Object.keys(settings)) {
|
for (let setting of Object.keys(settings)) {
|
||||||
lbry.setClientSetting(setting, settings[setting]);
|
lbry.setClientSetting(setting, settings[setting]);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
lbry.setClientSetting = function(setting, value) {
|
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.getSessionInfo = function(callback) {
|
||||||
lbry.call('get_lbry_session_info', {}, callback);
|
lbry.call('status', { session_status: true }, callback);
|
||||||
}
|
};
|
||||||
|
|
||||||
lbry.reportBug = function(message, callback) {
|
lbry.reportBug = function(message, callback) {
|
||||||
lbry.call('report_bug', {
|
lbry.call(
|
||||||
message: message
|
'report_bug',
|
||||||
}, callback);
|
{
|
||||||
}
|
message: message
|
||||||
|
},
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
//utilities
|
//utilities
|
||||||
lbry.formatCredits = function(amount, precision)
|
lbry.formatCredits = function(amount, precision) {
|
||||||
{
|
return amount.toFixed(precision || 1).replace(/\.?0+$/, '');
|
||||||
return amount.toFixed(precision || 1).replace(/\.?0+$/, '');
|
};
|
||||||
}
|
|
||||||
|
|
||||||
lbry.formatName = function(name) {
|
lbry.formatName = function(name) {
|
||||||
// Converts LBRY name to standard format (all lower case, no special characters, spaces replaced by dashes)
|
// Converts LBRY name to standard format (all lower case, no special characters, spaces replaced by dashes)
|
||||||
name = name.replace('/\s+/g', '-');
|
name = name.replace('/s+/g', '-');
|
||||||
name = name.toLowerCase().replace(/[^a-z0-9\-]/g, '');
|
name = name.toLowerCase().replace(/[^a-z0-9\-]/g, '');
|
||||||
return name;
|
return name;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
lbry.imagePath = function(file) {
|
||||||
lbry.imagePath = function(file)
|
return 'img/' + file;
|
||||||
{
|
};
|
||||||
return 'img/' + file;
|
|
||||||
}
|
|
||||||
|
|
||||||
lbry.getMediaType = function(contentType, fileName) {
|
lbry.getMediaType = function(contentType, fileName) {
|
||||||
if (contentType) {
|
if (contentType) {
|
||||||
return /^[^/]+/.exec(contentType)[0];
|
return /^[^/]+/.exec(contentType)[0];
|
||||||
} else if (fileName) {
|
} else if (fileName) {
|
||||||
var dotIndex = fileName.lastIndexOf('.');
|
var dotIndex = fileName.lastIndexOf('.');
|
||||||
if (dotIndex == -1) {
|
if (dotIndex == -1) {
|
||||||
return 'unknown';
|
return 'unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
var ext = fileName.substr(dotIndex + 1);
|
var ext = fileName.substr(dotIndex + 1);
|
||||||
if (/^mp4|mov|m4v|flv|f4v$/i.test(ext)) {
|
if (/^mp4|mov|m4v|flv|f4v$/i.test(ext)) {
|
||||||
return 'video';
|
return 'video';
|
||||||
} else if (/^mp3|m4a|aac|wav|flac|ogg$/i.test(ext)) {
|
} else if (/^mp3|m4a|aac|wav|flac|ogg$/i.test(ext)) {
|
||||||
return 'audio';
|
return 'audio';
|
||||||
} else if (/^html|htm|pdf|odf|doc|docx|md|markdown|txt$/i.test(ext)) {
|
} else if (/^html|htm|pdf|odf|doc|docx|md|markdown|txt$/i.test(ext)) {
|
||||||
return 'document';
|
return 'document';
|
||||||
} else {
|
} else {
|
||||||
return 'unknown';
|
return 'unknown';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return 'unknown';
|
return 'unknown';
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
lbry.stop = function(callback) {
|
lbry.stop = function(callback) {
|
||||||
lbry.call('stop', {}, callback);
|
lbry.call('stop', {}, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
lbry._subscribeIdCount = 0;
|
lbry._subscribeIdCount = 0;
|
||||||
|
@ -318,49 +384,58 @@ lbry._balanceSubscribeInterval = 5000;
|
||||||
|
|
||||||
lbry._balanceUpdateInterval = null;
|
lbry._balanceUpdateInterval = null;
|
||||||
lbry._updateBalanceSubscribers = function() {
|
lbry._updateBalanceSubscribers = function() {
|
||||||
lbry.wallet_balance().then(function(balance) {
|
lbry.wallet_balance().then(function(balance) {
|
||||||
for (let callback of Object.values(lbry._balanceSubscribeCallbacks)) {
|
for (let callback of Object.values(lbry._balanceSubscribeCallbacks)) {
|
||||||
callback(balance);
|
callback(balance);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!lbry._balanceUpdateInterval && Object.keys(lbry._balanceSubscribeCallbacks).length) {
|
if (
|
||||||
lbry._balanceUpdateInterval = setInterval(() => {
|
!lbry._balanceUpdateInterval &&
|
||||||
lbry._updateBalanceSubscribers();
|
Object.keys(lbry._balanceSubscribeCallbacks).length
|
||||||
}, lbry._balanceSubscribeInterval);
|
) {
|
||||||
}
|
lbry._balanceUpdateInterval = setInterval(() => {
|
||||||
}
|
lbry._updateBalanceSubscribers();
|
||||||
|
}, lbry._balanceSubscribeInterval);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
lbry.balanceSubscribe = function(callback) {
|
lbry.balanceSubscribe = function(callback) {
|
||||||
const subscribeId = ++lbry._subscribeIdCount;
|
const subscribeId = ++lbry._subscribeIdCount;
|
||||||
lbry._balanceSubscribeCallbacks[subscribeId] = callback;
|
lbry._balanceSubscribeCallbacks[subscribeId] = callback;
|
||||||
lbry._updateBalanceSubscribers();
|
lbry._updateBalanceSubscribers();
|
||||||
return subscribeId;
|
return subscribeId;
|
||||||
}
|
};
|
||||||
|
|
||||||
lbry.balanceUnsubscribe = function(subscribeId) {
|
lbry.balanceUnsubscribe = function(subscribeId) {
|
||||||
delete lbry._balanceSubscribeCallbacks[subscribeId];
|
delete lbry._balanceSubscribeCallbacks[subscribeId];
|
||||||
if (lbry._balanceUpdateInterval && !Object.keys(lbry._balanceSubscribeCallbacks).length) {
|
if (
|
||||||
clearInterval(lbry._balanceUpdateInterval)
|
lbry._balanceUpdateInterval &&
|
||||||
}
|
!Object.keys(lbry._balanceSubscribeCallbacks).length
|
||||||
}
|
) {
|
||||||
|
clearInterval(lbry._balanceUpdateInterval);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
lbry.showMenuIfNeeded = function() {
|
lbry.showMenuIfNeeded = function() {
|
||||||
const showingMenu = sessionStorage.getItem('menuShown') || null;
|
const showingMenu = sessionStorage.getItem('menuShown') || null;
|
||||||
const chosenMenu = lbry.getClientSetting('showDeveloperMenu') ? 'developer' : 'normal';
|
const chosenMenu = lbry.getClientSetting('showDeveloperMenu')
|
||||||
if (chosenMenu != showingMenu) {
|
? 'developer'
|
||||||
menu.showMenubar(chosenMenu == 'developer');
|
: 'normal';
|
||||||
}
|
if (chosenMenu != showingMenu) {
|
||||||
sessionStorage.setItem('menuShown', chosenMenu);
|
menu.showMenubar(chosenMenu == 'developer');
|
||||||
|
}
|
||||||
|
sessionStorage.setItem('menuShown', chosenMenu);
|
||||||
};
|
};
|
||||||
|
|
||||||
lbry.getAppVersionInfo = function() {
|
lbry.getAppVersionInfo = function() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
ipcRenderer.once('version-info-received', (event, versionInfo) => { resolve(versionInfo) });
|
ipcRenderer.once('version-info-received', (event, versionInfo) => {
|
||||||
ipcRenderer.send('version-info-requested');
|
resolve(versionInfo);
|
||||||
});
|
});
|
||||||
}
|
ipcRenderer.send('version-info-requested');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrappers for API methods to simulate missing or future behavior. Unlike the old-style stubs,
|
* Wrappers for API methods to simulate missing or future behavior. Unlike the old-style stubs,
|
||||||
|
@ -371,86 +446,118 @@ lbry.getAppVersionInfo = function() {
|
||||||
* Returns results from the file_list API method, plus dummy entries for pending publishes.
|
* 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.)
|
* (If a real publish with the same name is found, the pending publish will be ignored and removed.)
|
||||||
*/
|
*/
|
||||||
lbry.file_list = function(params={}) {
|
lbry.file_list = function(params = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const {name, channel_name, outpoint} = params;
|
const { name, channel_name, outpoint } = params;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If we're searching by outpoint, check first to see if there's a matching pending publish.
|
* 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
|
* Pending publishes use their own faux outpoints that are always unique, so we don't need
|
||||||
* to check if there's a real file.
|
* to check if there's a real file.
|
||||||
*/
|
*/
|
||||||
if (outpoint) {
|
if (outpoint) {
|
||||||
const pendingPublish = getPendingPublish({outpoint});
|
const pendingPublish = getPendingPublish({ outpoint });
|
||||||
if (pendingPublish) {
|
if (pendingPublish) {
|
||||||
resolve([pendingPublishToDummyFileInfo(pendingPublish)]);
|
resolve([pendingPublishToDummyFileInfo(pendingPublish)]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lbry.call('file_list', params, (fileInfos) => {
|
lbry.call(
|
||||||
removePendingPublishIfNeeded({name, channel_name, outpoint});
|
'file_list',
|
||||||
|
params,
|
||||||
|
fileInfos => {
|
||||||
|
removePendingPublishIfNeeded({ name, channel_name, outpoint });
|
||||||
|
|
||||||
const dummyFileInfos = lbry.getPendingPublishes().map(pendingPublishToDummyFileInfo);
|
const dummyFileInfos = lbry
|
||||||
resolve([...fileInfos, ...dummyFileInfos]);
|
.getPendingPublishes()
|
||||||
}, reject, reject);
|
.map(pendingPublishToDummyFileInfo);
|
||||||
});
|
resolve([...fileInfos, ...dummyFileInfos]);
|
||||||
}
|
},
|
||||||
|
reject,
|
||||||
|
reject
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
lbry.claim_list_mine = function(params={}) {
|
lbry.claim_list_mine = function(params = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
lbry.call('claim_list_mine', params, (claims) => {
|
lbry.call(
|
||||||
for (let {name, channel_name, txid, nout} of claims) {
|
'claim_list_mine',
|
||||||
removePendingPublishIfNeeded({name, channel_name, outpoint: txid + ':' + nout});
|
params,
|
||||||
}
|
claims => {
|
||||||
|
for (let { name, channel_name, txid, nout } of claims) {
|
||||||
|
removePendingPublishIfNeeded({
|
||||||
|
name,
|
||||||
|
channel_name,
|
||||||
|
outpoint: txid + ':' + nout
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const dummyClaims = lbry.getPendingPublishes().map(pendingPublishToDummyClaim);
|
const dummyClaims = lbry
|
||||||
resolve([...claims, ...dummyClaims]);
|
.getPendingPublishes()
|
||||||
}, reject, reject)
|
.map(pendingPublishToDummyClaim);
|
||||||
});
|
resolve([...claims, ...dummyClaims]);
|
||||||
}
|
},
|
||||||
|
reject,
|
||||||
|
reject
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const claimCacheKey = 'resolve_claim_cache';
|
const claimCacheKey = 'resolve_claim_cache';
|
||||||
lbry._claimCache = getSession(claimCacheKey, {});
|
lbry._claimCache = getSession(claimCacheKey, {});
|
||||||
lbry._resolveXhrs = {}
|
lbry._resolveXhrs = {};
|
||||||
lbry.resolve = function(params={}) {
|
lbry.resolve = function(params = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!params.uri) {
|
if (!params.uri) {
|
||||||
throw "Resolve has hacked cache on top of it that requires a URI"
|
throw __('Resolve has hacked cache on top of it that requires a URI');
|
||||||
}
|
}
|
||||||
if (params.uri && lbry._claimCache[params.uri] !== undefined) {
|
if (params.uri && lbry._claimCache[params.uri] !== undefined) {
|
||||||
resolve(lbry._claimCache[params.uri]);
|
resolve(lbry._claimCache[params.uri]);
|
||||||
} else {
|
} else {
|
||||||
lbry._resolveXhrs[params.uri] = lbry.call('resolve', params, function(data) {
|
lbry._resolveXhrs[params.uri] = lbry.call(
|
||||||
if (data !== undefined) {
|
'resolve',
|
||||||
lbry._claimCache[params.uri] = data;
|
params,
|
||||||
}
|
function(data) {
|
||||||
setSession(claimCacheKey, lbry._claimCache)
|
if (data !== undefined) {
|
||||||
resolve(data)
|
lbry._claimCache[params.uri] = data;
|
||||||
}, reject)
|
}
|
||||||
}
|
setSession(claimCacheKey, lbry._claimCache);
|
||||||
});
|
resolve(data);
|
||||||
}
|
},
|
||||||
|
reject
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
lbry.cancelResolve = function(params={}) {
|
lbry.cancelResolve = function(params = {}) {
|
||||||
const xhr = lbry._resolveXhrs[params.uri]
|
const xhr = lbry._resolveXhrs[params.uri];
|
||||||
if (xhr && xhr.readyState > 0 && xhr.readyState < 4) {
|
if (xhr && xhr.readyState > 0 && xhr.readyState < 4) {
|
||||||
xhr.abort()
|
xhr.abort();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
lbry = new Proxy(lbry, {
|
lbry = new Proxy(lbry, {
|
||||||
get: function(target, name) {
|
get: function(target, name) {
|
||||||
if (name in target) {
|
if (name in target) {
|
||||||
return target[name];
|
return target[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
return function(params={}) {
|
return function(params = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
jsonrpc.call(lbry.daemonConnectionString, name, params, resolve, reject, reject);
|
jsonrpc.call(
|
||||||
});
|
lbry.daemonConnectionString,
|
||||||
};
|
name,
|
||||||
}
|
params,
|
||||||
|
resolve,
|
||||||
|
reject,
|
||||||
|
reject
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default lbry;
|
export default lbry;
|
||||||
|
|
213
ui/js/lbryio.js
213
ui/js/lbryio.js
|
@ -1,56 +1,61 @@
|
||||||
import {getSession, setSession} from './utils.js';
|
import { getSession, setSession, setLocal } from "./utils.js";
|
||||||
import lbry from './lbry.js';
|
import lbry from "./lbry.js";
|
||||||
|
|
||||||
const querystring = require('querystring');
|
const querystring = require("querystring");
|
||||||
|
|
||||||
const lbryio = {
|
const lbryio = {
|
||||||
_accessToken: getSession('accessToken'),
|
_accessToken: getSession("accessToken"),
|
||||||
_authenticationPromise: null,
|
_authenticationPromise: null,
|
||||||
_user : null,
|
enabled: true,
|
||||||
enabled: true
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CONNECTION_STRING = process.env.LBRY_APP_API_URL
|
||||||
const CONNECTION_STRING = process.env.LBRY_APP_API_URL ?
|
? process.env.LBRY_APP_API_URL.replace(/\/*$/, "/") // exactly one slash at the end
|
||||||
process.env.LBRY_APP_API_URL.replace(/\/*$/,'/') : // exactly one slash at the end
|
: "https://api.lbry.io/";
|
||||||
'https://api.lbry.io/'
|
|
||||||
const EXCHANGE_RATE_TIMEOUT = 20 * 60 * 1000;
|
const EXCHANGE_RATE_TIMEOUT = 20 * 60 * 1000;
|
||||||
|
|
||||||
lbryio._exchangePromise = null;
|
lbryio._exchangePromise = null;
|
||||||
lbryio._exchangeLastFetched = null;
|
lbryio._exchangeLastFetched = null;
|
||||||
lbryio.getExchangeRates = function() {
|
lbryio.getExchangeRates = function() {
|
||||||
if (!lbryio._exchangeLastFetched || Date.now() - lbryio._exchangeLastFetched > EXCHANGE_RATE_TIMEOUT) {
|
if (
|
||||||
|
!lbryio._exchangeLastFetched ||
|
||||||
|
Date.now() - lbryio._exchangeLastFetched > EXCHANGE_RATE_TIMEOUT
|
||||||
|
) {
|
||||||
lbryio._exchangePromise = new Promise((resolve, reject) => {
|
lbryio._exchangePromise = new Promise((resolve, reject) => {
|
||||||
lbryio.call('lbc', 'exchange_rate', {}, 'get', true).then(({lbc_usd, lbc_btc, btc_usd}) => {
|
lbryio
|
||||||
const rates = {lbc_usd, lbc_btc, btc_usd};
|
.call("lbc", "exchange_rate", {}, "get", true)
|
||||||
resolve(rates);
|
.then(({ lbc_usd, lbc_btc, btc_usd }) => {
|
||||||
}).catch(reject);
|
const rates = { lbc_usd, lbc_btc, btc_usd };
|
||||||
|
resolve(rates);
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
});
|
});
|
||||||
lbryio._exchangeLastFetched = Date.now();
|
lbryio._exchangeLastFetched = Date.now();
|
||||||
}
|
}
|
||||||
return lbryio._exchangePromise;
|
return lbryio._exchangePromise;
|
||||||
}
|
};
|
||||||
|
|
||||||
lbryio.call = function(resource, action, params={}, method='get', evenIfDisabled=false) { // evenIfDisabled is just for development, when we may have some calls working and some not
|
lbryio.call = function(resource, action, params = {}, method = "get") {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!lbryio.enabled && !evenIfDisabled && (resource != 'discover' || action != 'list')) {
|
if (!lbryio.enabled && (resource != "discover" || action != "list")) {
|
||||||
console.log("Internal API disabled");
|
console.log(__("Internal API disabled"));
|
||||||
reject(new Error("LBRY internal API is disabled"))
|
reject(new Error(__("LBRY internal API is disabled")));
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest;
|
const xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
xhr.addEventListener('error', function (event) {
|
xhr.addEventListener("error", function(event) {
|
||||||
reject(new Error("Something went wrong making an internal API call."));
|
reject(
|
||||||
|
new Error(__("Something went wrong making an internal API call."))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
xhr.addEventListener("timeout", function() {
|
||||||
xhr.addEventListener('timeout', function() {
|
reject(new Error(__("XMLHttpRequest connection timed out")));
|
||||||
reject(new Error('XMLHttpRequest connection timed out'));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
xhr.addEventListener('load', function() {
|
xhr.addEventListener("load", function() {
|
||||||
const response = JSON.parse(xhr.responseText);
|
const response = JSON.parse(xhr.responseText);
|
||||||
|
|
||||||
if (!response.success) {
|
if (!response.success) {
|
||||||
|
@ -59,15 +64,17 @@ lbryio.call = function(resource, action, params={}, method='get', evenIfDisabled
|
||||||
error.xhr = xhr;
|
error.xhr = xhr;
|
||||||
reject(error);
|
reject(error);
|
||||||
} else {
|
} else {
|
||||||
document.dispatchEvent(new CustomEvent('unhandledError', {
|
document.dispatchEvent(
|
||||||
detail: {
|
new CustomEvent("unhandledError", {
|
||||||
connectionString: connectionString,
|
detail: {
|
||||||
method: action,
|
connectionString: connectionString,
|
||||||
params: params,
|
method: action,
|
||||||
message: response.error.message,
|
params: params,
|
||||||
... response.error.data ? {data: response.error.data} : {},
|
message: response.error.message,
|
||||||
}
|
...(response.error.data ? { data: response.error.data } : {}),
|
||||||
}));
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
resolve(response.data);
|
resolve(response.data);
|
||||||
|
@ -79,85 +86,107 @@ lbryio.call = function(resource, action, params={}, method='get', evenIfDisabled
|
||||||
//const fullParams = {...params, ... accessToken ? {access_token: accessToken} : {}};
|
//const fullParams = {...params, ... accessToken ? {access_token: accessToken} : {}};
|
||||||
|
|
||||||
// Temp app ID based auth:
|
// Temp app ID based auth:
|
||||||
const fullParams = {app_id: lbryio.getAccessToken(), ...params};
|
const fullParams = { app_id: lbryio.getAccessToken(), ...params };
|
||||||
|
|
||||||
if (method == 'get') {
|
if (method == "get") {
|
||||||
xhr.open('get', CONNECTION_STRING + resource + '/' + action + '?' + querystring.stringify(fullParams), true);
|
xhr.open(
|
||||||
|
"get",
|
||||||
|
CONNECTION_STRING +
|
||||||
|
resource +
|
||||||
|
"/" +
|
||||||
|
action +
|
||||||
|
"?" +
|
||||||
|
querystring.stringify(fullParams),
|
||||||
|
true
|
||||||
|
);
|
||||||
xhr.send();
|
xhr.send();
|
||||||
} else if (method == 'post') {
|
} else if (method == "post") {
|
||||||
xhr.open('post', CONNECTION_STRING + resource + '/' + action, true);
|
xhr.open("post", CONNECTION_STRING + resource + "/" + action, true);
|
||||||
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||||
xhr.send(querystring.stringify(fullParams));
|
xhr.send(querystring.stringify(fullParams));
|
||||||
} else {
|
} else {
|
||||||
reject(new Error("Invalid method"));
|
reject(new Error(__("Invalid method")));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
lbryio.getAccessToken = () => {
|
lbryio.getAccessToken = () => {
|
||||||
const token = getSession('accessToken');
|
const token = getSession("accessToken");
|
||||||
return token ? token.toString().trim() : token;
|
return token ? token.toString().trim() : token;
|
||||||
}
|
};
|
||||||
|
|
||||||
lbryio.setAccessToken = (token) => {
|
lbryio.setAccessToken = token => {
|
||||||
setSession('accessToken', token ? token.toString().trim() : token)
|
setSession("accessToken", token ? token.toString().trim() : token);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
lbryio.setCurrentUser = (resolve, reject) => {
|
||||||
|
lbryio
|
||||||
|
.call("user", "me")
|
||||||
|
.then(data => {
|
||||||
|
resolve(data);
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
lbryio.setAccessToken(null);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
lbryio.authenticate = function() {
|
lbryio.authenticate = function() {
|
||||||
if (!lbryio.enabled) {
|
if (!lbryio.enabled) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
resolve({
|
resolve({
|
||||||
id: 1,
|
id: 1,
|
||||||
has_verified_email: true
|
language: "en",
|
||||||
})
|
has_email: true,
|
||||||
})
|
has_verified_email: true,
|
||||||
|
is_reward_approved: false,
|
||||||
|
is_reward_eligible: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (lbryio._authenticationPromise === null) {
|
if (lbryio._authenticationPromise === null) {
|
||||||
lbryio._authenticationPromise = new Promise((resolve, reject) => {
|
lbryio._authenticationPromise = new Promise((resolve, reject) => {
|
||||||
lbry.status().then((response) => {
|
lbry
|
||||||
|
.status()
|
||||||
|
.then(response => {
|
||||||
|
let installation_id = response.installation_id;
|
||||||
|
|
||||||
let installation_id = response.installation_id;
|
if (!lbryio.getAccessToken()) {
|
||||||
|
lbryio
|
||||||
function setCurrentUser() {
|
.call(
|
||||||
lbryio.call('user', 'me').then((data) => {
|
"user",
|
||||||
lbryio.user = data
|
"new",
|
||||||
resolve(data)
|
{
|
||||||
}).catch(function(err) {
|
language: "en",
|
||||||
lbryio.setAccessToken(null);
|
app_id: installation_id,
|
||||||
if (!getSession('reloadedOnFailedAuth')) {
|
},
|
||||||
setSession('reloadedOnFailedAuth', true)
|
"post"
|
||||||
window.location.reload();
|
)
|
||||||
} else {
|
.then(function(responseData) {
|
||||||
reject(err);
|
if (!responseData.id) {
|
||||||
}
|
reject(
|
||||||
})
|
new Error("Received invalid authentication response.")
|
||||||
}
|
);
|
||||||
|
}
|
||||||
if (!lbryio.getAccessToken()) {
|
lbryio.setAccessToken(installation_id);
|
||||||
lbryio.call('user', 'new', {
|
lbryio.setCurrentUser(resolve, reject);
|
||||||
language: 'en',
|
})
|
||||||
app_id: installation_id,
|
.catch(function(error) {
|
||||||
}, 'post').then(function(responseData) {
|
/*
|
||||||
if (!responseData.id) {
|
until we have better error code format, assume all errors are duplicate application id
|
||||||
reject(new Error("Received invalid authentication response."));
|
if we're wrong, this will be caught by later attempts to make a valid call
|
||||||
}
|
*/
|
||||||
lbryio.setAccessToken(installation_id)
|
lbryio.setAccessToken(installation_id);
|
||||||
setCurrentUser()
|
lbryio.setCurrentUser(resolve, reject);
|
||||||
}).catch(function(error) {
|
});
|
||||||
/*
|
} else {
|
||||||
until we have better error code format, assume all errors are duplicate application id
|
lbryio.setCurrentUser(resolve, reject);
|
||||||
if we're wrong, this will be caught by later attempts to make a valid call
|
}
|
||||||
*/
|
})
|
||||||
lbryio.setAccessToken(installation_id)
|
.catch(reject);
|
||||||
setCurrentUser()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
setCurrentUser()
|
|
||||||
}
|
|
||||||
}).catch(reject);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return lbryio._authenticationPromise;
|
return lbryio._authenticationPromise;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default lbryio;
|
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
|
* - contentName (string): For anon claims, the name; for channel claims, the path
|
||||||
* - channelName (string, if present): Channel name without @
|
* - channelName (string, if present): Channel name without @
|
||||||
*/
|
*/
|
||||||
lbryuri.parse = function(uri, requireProto=false) {
|
lbryuri.parse = function(uri, requireProto = false) {
|
||||||
// Break into components. Empty sub-matches are converted to null
|
// Break into components. Empty sub-matches are converted to null
|
||||||
const componentsRegex = new RegExp(
|
const componentsRegex = new RegExp(
|
||||||
'^((?:lbry:\/\/)?)' + // protocol
|
'^((?:lbry://)?)' + // protocol
|
||||||
'([^:$#/]*)' + // name (stops at the first separator or end)
|
'([^:$#/]*)' + // name (stops at the first separator or end)
|
||||||
'([:$#]?)([^/]*)' + // modifier separator, modifier (stops at the first path separator or end)
|
'([:$#]?)([^/]*)' + // modifier separator, modifier (stops at the first path separator or end)
|
||||||
'(/?)(.*)' // path separator, path
|
'(/?)(.*)' // path separator, path
|
||||||
);
|
);
|
||||||
const [proto, name, modSep, modVal, pathSep, path] = componentsRegex.exec(uri).slice(1).map(match => match || null);
|
const [proto, name, modSep, modVal, pathSep, path] = componentsRegex
|
||||||
|
.exec(uri)
|
||||||
|
.slice(1)
|
||||||
|
.map(match => match || null);
|
||||||
|
|
||||||
let contentName;
|
let contentName;
|
||||||
|
|
||||||
// Validate protocol
|
// Validate protocol
|
||||||
if (requireProto && !proto) {
|
if (requireProto && !proto) {
|
||||||
throw new Error('LBRY URIs must include a protocol prefix (lbry://).');
|
throw new Error(__('LBRY URIs must include a protocol prefix (lbry://).'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate and process name
|
// Validate and process name
|
||||||
if (!name) {
|
if (!name) {
|
||||||
throw new Error('URI does not include name.');
|
throw new Error(__('URI does not include name.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const isChannel = name.startsWith('@');
|
const isChannel = name.startsWith('@');
|
||||||
const channelName = isChannel ? name.slice(1) : name;
|
const channelName = isChannel ? name.slice(1) : name;
|
||||||
|
|
||||||
if (isChannel) {
|
if (isChannel) {
|
||||||
if (!channelName) {
|
if (!channelName) {
|
||||||
throw new Error('No channel name after @.');
|
throw new Error(__('No channel name after @.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channelName.length < CHANNEL_NAME_MIN_LEN) {
|
if (channelName.length < CHANNEL_NAME_MIN_LEN) {
|
||||||
throw new Error(`Channel names must be at least ${CHANNEL_NAME_MIN_LEN} characters.`);
|
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);
|
const nameBadChars = (channelName || name).match(/[^A-Za-z0-9-]/g);
|
||||||
if (nameBadChars) {
|
if (nameBadChars) {
|
||||||
throw new Error(`Invalid character${nameBadChars.length == 1 ? '' : 's'} in name: ${nameBadChars.join(', ')}.`);
|
throw new Error(
|
||||||
}
|
__(
|
||||||
|
`Invalid character %s in name: %s.`,
|
||||||
|
nameBadChars.length == 1 ? '' : 's',
|
||||||
|
nameBadChars.join(', ')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Validate and process modifier (claim ID, bid position or claim sequence)
|
// Validate and process modifier (claim ID, bid position or claim sequence)
|
||||||
let claimId, claimSequence, bidPosition;
|
let claimId, claimSequence, bidPosition;
|
||||||
if (modSep) {
|
if (modSep) {
|
||||||
if (!modVal) {
|
if (!modVal) {
|
||||||
throw new Error(`No modifier provided after separator ${modSep}.`);
|
throw new Error(__(`No modifier provided after separator %s.`, modSep));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modSep == '#') {
|
if (modSep == '#') {
|
||||||
claimId = modVal;
|
claimId = modVal;
|
||||||
} else if (modSep == ':') {
|
} else if (modSep == ':') {
|
||||||
claimSequence = modVal;
|
claimSequence = modVal;
|
||||||
} else if (modSep == '$') {
|
} else if (modSep == '$') {
|
||||||
bidPosition = modVal;
|
bidPosition = modVal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (claimId && (claimId.length > CLAIM_ID_MAX_LEN || !claimId.match(/^[0-9a-f]+$/))) {
|
if (
|
||||||
throw new Error(`Invalid claim ID ${claimId}.`);
|
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]*$/)) {
|
if (claimSequence && !claimSequence.match(/^-?[1-9][0-9]*$/)) {
|
||||||
throw new Error('Claim sequence must be a number.');
|
throw new Error(__('Claim sequence must be a number.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bidPosition && !bidPosition.match(/^-?[1-9][0-9]*$/)) {
|
if (bidPosition && !bidPosition.match(/^-?[1-9][0-9]*$/)) {
|
||||||
throw new Error('Bid position must be a number.');
|
throw new Error(__('Bid position must be a number.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate and process path
|
// Validate and process path
|
||||||
if (path) {
|
if (path) {
|
||||||
if (!isChannel) {
|
if (!isChannel) {
|
||||||
throw new Error('Only channel URIs may have a path.');
|
throw new Error(__('Only channel URIs may have a path.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathBadChars = path.match(/[^A-Za-z0-9-]/g);
|
const pathBadChars = path.match(/[^A-Za-z0-9-]/g);
|
||||||
if (pathBadChars) {
|
if (pathBadChars) {
|
||||||
throw new Error(`Invalid character${count == 1 ? '' : 's'} in path: ${nameBadChars.join(', ')}`);
|
throw new Error(
|
||||||
}
|
__(
|
||||||
|
`Invalid character %s in path: %s`,
|
||||||
|
count == 1 ? '' : 's',
|
||||||
|
nameBadChars.join(', ')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
contentName = path;
|
contentName = path;
|
||||||
} else if (pathSep) {
|
} else if (pathSep) {
|
||||||
throw new Error('No path provided after /');
|
throw new Error(__('No path provided after /'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name, path, isChannel,
|
name,
|
||||||
... contentName ? {contentName} : {},
|
path,
|
||||||
... channelName ? {channelName} : {},
|
isChannel,
|
||||||
... claimSequence ? {claimSequence: parseInt(claimSequence)} : {},
|
...(contentName ? { contentName } : {}),
|
||||||
... bidPosition ? {bidPosition: parseInt(bidPosition)} : {},
|
...(channelName ? { channelName } : {}),
|
||||||
... claimId ? {claimId} : {},
|
...(claimSequence ? { claimSequence: parseInt(claimSequence) } : {}),
|
||||||
... path ? {path} : {},
|
...(bidPosition ? { bidPosition: parseInt(bidPosition) } : {}),
|
||||||
};
|
...(claimId ? { claimId } : {}),
|
||||||
}
|
...(path ? { path } : {})
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes an object in the same format returned by lbryuri.parse() and builds a URI.
|
* 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.
|
* The channelName key will accept names with or without the @ prefix.
|
||||||
*/
|
*/
|
||||||
lbryuri.build = function(uriObj, includeProto=true, allowExtraProps=false) {
|
lbryuri.build = function(uriObj, includeProto = true, allowExtraProps = false) {
|
||||||
let {name, claimId, claimSequence, bidPosition, path, contentName, channelName} = uriObj;
|
let {
|
||||||
|
name,
|
||||||
|
claimId,
|
||||||
|
claimSequence,
|
||||||
|
bidPosition,
|
||||||
|
path,
|
||||||
|
contentName,
|
||||||
|
channelName
|
||||||
|
} = uriObj;
|
||||||
|
|
||||||
if (channelName) {
|
if (channelName) {
|
||||||
const channelNameFormatted = channelName.startsWith('@') ? channelName : '@' + channelName;
|
const channelNameFormatted = channelName.startsWith('@')
|
||||||
if (!name) {
|
? channelName
|
||||||
name = channelNameFormatted;
|
: '@' + channelName;
|
||||||
} else if (name !== channelNameFormatted) {
|
if (!name) {
|
||||||
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.');
|
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 (contentName) {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
name = contentName;
|
name = contentName;
|
||||||
} else if (!path) {
|
} else if (!path) {
|
||||||
path = contentName;
|
path = contentName;
|
||||||
}
|
}
|
||||||
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.');
|
throw new Error(
|
||||||
}
|
__(
|
||||||
}
|
'Path and contentName do not match. Only one is required; most likely you wanted contentName.'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (includeProto ? 'lbry://' : '') + name +
|
return (
|
||||||
(claimId ? `#${claimId}` : '') +
|
(includeProto ? 'lbry://' : '') +
|
||||||
(claimSequence ? `:${claimSequence}` : '') +
|
name +
|
||||||
(bidPosition ? `\$${bidPosition}` : '') +
|
(claimId ? `#${claimId}` : '') +
|
||||||
(path ? `/${path}` : '');
|
(claimSequence ? `:${claimSequence}` : '') +
|
||||||
|
(bidPosition ? `\$${bidPosition}` : '') +
|
||||||
}
|
(path ? `/${path}` : '')
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/* Takes a parseable LBRY URI and converts it to standard, canonical format (currently this just
|
/* Takes a parseable LBRY URI and converts it to standard, canonical format (currently this just
|
||||||
* consists of adding the lbry:// prefix if needed) */
|
* consists of adding the lbry:// prefix if needed) */
|
||||||
lbryuri.normalize= function(uri) {
|
lbryuri.normalize = function(uri) {
|
||||||
const {name, path, bidPosition, claimSequence, claimId} = lbryuri.parse(uri);
|
const { name, path, bidPosition, claimSequence, claimId } = lbryuri.parse(
|
||||||
return lbryuri.build({name, path, claimSequence, bidPosition, claimId});
|
uri
|
||||||
}
|
);
|
||||||
|
return lbryuri.build({ name, path, claimSequence, bidPosition, claimId });
|
||||||
|
};
|
||||||
|
|
||||||
lbryuri.isValid = function(uri) {
|
lbryuri.isValid = function(uri) {
|
||||||
let parts
|
let parts;
|
||||||
try {
|
try {
|
||||||
parts = lbryuri.parse(lbryuri.normalize(uri))
|
parts = lbryuri.parse(lbryuri.normalize(uri));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return parts && parts.name;
|
return parts && parts.name;
|
||||||
}
|
};
|
||||||
|
|
||||||
lbryuri.isValidName = function(name, checkCase=true) {
|
lbryuri.isValidName = function(name, checkCase = true) {
|
||||||
const regexp = new RegExp('^[a-z0-9-]+$', checkCase ? '' : 'i');
|
const regexp = new RegExp('^[a-z0-9-]+$', checkCase ? '' : 'i');
|
||||||
return regexp.test(name);
|
return regexp.test(name);
|
||||||
}
|
};
|
||||||
|
|
||||||
lbryuri.isClaimable = function(uri) {
|
lbryuri.isClaimable = function(uri) {
|
||||||
let parts
|
let parts;
|
||||||
try {
|
try {
|
||||||
parts = lbryuri.parse(lbryuri.normalize(uri))
|
parts = lbryuri.parse(lbryuri.normalize(uri));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return parts && parts.name && !parts.claimId && !parts.bidPosition && !parts.claimSequence && !parts.isChannel && !parts.path;
|
return (
|
||||||
}
|
parts &&
|
||||||
|
parts.name &&
|
||||||
|
!parts.claimId &&
|
||||||
|
!parts.bidPosition &&
|
||||||
|
!parts.claimSequence &&
|
||||||
|
!parts.isChannel &&
|
||||||
|
!parts.path
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
window.lbryuri = lbryuri;
|
window.lbryuri = lbryuri;
|
||||||
export default lbryuri;
|
export default lbryuri;
|
||||||
|
|
|
@ -4,9 +4,9 @@ import jsonrpc from './jsonrpc.js';
|
||||||
const queryTimeout = 3000;
|
const queryTimeout = 3000;
|
||||||
const maxQueryTries = 2;
|
const maxQueryTries = 2;
|
||||||
const defaultServers = [
|
const defaultServers = [
|
||||||
'http://lighthouse7.lbry.io:50005',
|
'http://lighthouse7.lbry.io:50005',
|
||||||
'http://lighthouse8.lbry.io:50005',
|
'http://lighthouse8.lbry.io:50005',
|
||||||
'http://lighthouse9.lbry.io:50005',
|
'http://lighthouse9.lbry.io:50005'
|
||||||
];
|
];
|
||||||
const path = '/';
|
const path = '/';
|
||||||
|
|
||||||
|
@ -14,48 +14,71 @@ let server = null;
|
||||||
let connectTryNum = 0;
|
let connectTryNum = 0;
|
||||||
|
|
||||||
function getServers() {
|
function getServers() {
|
||||||
return lbry.getClientSetting('useCustomLighthouseServers')
|
return lbry.getClientSetting('useCustomLighthouseServers')
|
||||||
? lbry.getClientSetting('customLighthouseServers')
|
? lbry.getClientSetting('customLighthouseServers')
|
||||||
: defaultServers;
|
: defaultServers;
|
||||||
}
|
}
|
||||||
|
|
||||||
function call(method, params, callback, errorCallback) {
|
function call(method, params, callback, errorCallback) {
|
||||||
if (connectTryNum >= maxQueryTries) {
|
if (connectTryNum >= maxQueryTries) {
|
||||||
errorCallback(new Error(`Could not connect to Lighthouse server. Last server attempted: ${server}`));
|
errorCallback(
|
||||||
return;
|
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 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
|
* set of servers (most likely because of a settings change), or we're re-trying after a failed
|
||||||
* query.
|
* query.
|
||||||
*/
|
*/
|
||||||
if (!server || !getServers().includes(server) || connectTryNum > 0) {
|
if (!server || !getServers().includes(server) || connectTryNum > 0) {
|
||||||
// If there's a current server, filter it out so we get a new one
|
// If there's a current server, filter it out so we get a new one
|
||||||
const newServerChoices = server ? getServers().filter((s) => s != server) : getServers();
|
const newServerChoices = server
|
||||||
server = newServerChoices[Math.round(Math.random() * (newServerChoices.length - 1))];
|
? getServers().filter(s => s != server)
|
||||||
}
|
: getServers();
|
||||||
|
server =
|
||||||
|
newServerChoices[
|
||||||
|
Math.round(Math.random() * (newServerChoices.length - 1))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
jsonrpc.call(server + path, method, params, (response) => {
|
jsonrpc.call(
|
||||||
connectTryNum = 0;
|
server + path,
|
||||||
callback(response);
|
method,
|
||||||
}, (error) => {
|
params,
|
||||||
connectTryNum = 0;
|
response => {
|
||||||
errorCallback(error);
|
connectTryNum = 0;
|
||||||
}, () => {
|
callback(response);
|
||||||
connectTryNum++;
|
},
|
||||||
call(method, params, callback, errorCallback);
|
error => {
|
||||||
}, queryTimeout);
|
connectTryNum = 0;
|
||||||
|
errorCallback(error);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
connectTryNum++;
|
||||||
|
call(method, params, callback, errorCallback);
|
||||||
|
},
|
||||||
|
queryTimeout
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const lighthouse = new Proxy({}, {
|
const lighthouse = new Proxy(
|
||||||
get: function(target, name) {
|
{},
|
||||||
return function(...params) {
|
{
|
||||||
return new Promise((resolve, reject) => {
|
get: function(target, name) {
|
||||||
call(name, params, resolve, reject);
|
return function(...params) {
|
||||||
});
|
return new Promise((resolve, reject) => {
|
||||||
};
|
call(name, params, resolve, reject);
|
||||||
},
|
});
|
||||||
});
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default lighthouse;
|
export default lighthouse;
|
||||||
|
|
118
ui/js/main.js
118
ui/js/main.js
|
@ -1,67 +1,61 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from "react-dom";
|
||||||
import lbry from './lbry.js';
|
import lbry from "./lbry.js";
|
||||||
import lbryio from './lbryio.js';
|
import App from "component/app/index.js";
|
||||||
import lighthouse from './lighthouse.js';
|
import SnackBar from "component/snackBar";
|
||||||
import App from 'component/app/index.js';
|
import { Provider } from "react-redux";
|
||||||
import SplashScreen from 'component/splash.js';
|
import store from "store.js";
|
||||||
import SnackBar from 'component/snackBar';
|
import SplashScreen from "component/splash.js";
|
||||||
import {AuthOverlay} from 'component/auth.js';
|
import AuthOverlay from "component/authOverlay";
|
||||||
import { Provider } from 'react-redux';
|
import { doChangePath, doNavigate, doDaemonReady } from "actions/app";
|
||||||
import store from 'store.js';
|
import { toQueryString } from "util/query_params";
|
||||||
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 env = ENV;
|
||||||
const contextMenu = remote.require('./menu/context-menu');
|
const { remote, ipcRenderer, shell } = require("electron");
|
||||||
const app = require('./app')
|
const contextMenu = remote.require("./menu/context-menu");
|
||||||
|
const app = require("./app");
|
||||||
|
|
||||||
lbry.showMenuIfNeeded();
|
lbry.showMenuIfNeeded();
|
||||||
|
|
||||||
window.addEventListener('contextmenu', (event) => {
|
window.addEventListener("contextmenu", event => {
|
||||||
contextMenu.showContextMenu(remote.getCurrentWindow(), event.x, event.y,
|
contextMenu.showContextMenu(
|
||||||
lbry.getClientSetting('showDeveloperMenu'));
|
remote.getCurrentWindow(),
|
||||||
|
event.x,
|
||||||
|
event.y,
|
||||||
|
lbry.getClientSetting("showDeveloperMenu")
|
||||||
|
);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('popstate', (event, param) => {
|
window.addEventListener("popstate", (event, param) => {
|
||||||
const params = event.state
|
const params = event.state;
|
||||||
const pathParts = document.location.pathname.split('/')
|
const pathParts = document.location.pathname.split("/");
|
||||||
const route = '/' + pathParts[pathParts.length - 1]
|
const route = "/" + pathParts[pathParts.length - 1];
|
||||||
const queryString = toQueryString(params)
|
const queryString = toQueryString(params);
|
||||||
|
|
||||||
let action
|
let action;
|
||||||
if (route.match(/html$/)) {
|
if (route.match(/html$/)) {
|
||||||
action = doChangePath('/discover')
|
action = doChangePath("/discover");
|
||||||
} else {
|
} else {
|
||||||
action = doChangePath(`${route}?${queryString}`)
|
action = doChangePath(`${route}?${queryString}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
app.store.dispatch(action)
|
app.store.dispatch(action);
|
||||||
})
|
});
|
||||||
|
|
||||||
ipcRenderer.on('open-uri-requested', (event, uri) => {
|
ipcRenderer.on("open-uri-requested", (event, uri) => {
|
||||||
if (uri && uri.startsWith('lbry://')) {
|
if (uri && uri.startsWith("lbry://")) {
|
||||||
app.store.dispatch(doNavigate('/show', { uri }))
|
app.store.dispatch(doNavigate("/show", { uri }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('click', (event) => {
|
document.addEventListener("click", event => {
|
||||||
var target = event.target;
|
var target = event.target;
|
||||||
while (target && target !== document) {
|
while (target && target !== document) {
|
||||||
if (target.matches('a[href^="http"]')) {
|
if (
|
||||||
|
target.matches('a[href^="http"]') ||
|
||||||
|
target.matches('a[href^="mailto"]')
|
||||||
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
shell.openExternal(target.href);
|
shell.openExternal(target.href);
|
||||||
return;
|
return;
|
||||||
|
@ -72,21 +66,33 @@ document.addEventListener('click', (event) => {
|
||||||
|
|
||||||
const initialState = app.store.getState();
|
const initialState = app.store.getState();
|
||||||
|
|
||||||
|
// import whyDidYouUpdate from "why-did-you-update";
|
||||||
|
// if (env === "development") {
|
||||||
|
// /*
|
||||||
|
// https://github.com/garbles/why-did-you-update
|
||||||
|
// "A function that monkey patches React and notifies you in the console when
|
||||||
|
// potentially unnecessary re-renders occur."
|
||||||
|
//
|
||||||
|
// Just checks if props change between updates. Can be fixed by manually
|
||||||
|
// adding a check in shouldComponentUpdate or using React.PureComponent
|
||||||
|
// */
|
||||||
|
// whyDidYouUpdate(React);
|
||||||
|
// }
|
||||||
|
|
||||||
var init = function() {
|
var init = function() {
|
||||||
|
|
||||||
function onDaemonReady() {
|
function onDaemonReady() {
|
||||||
window.sessionStorage.setItem('loaded', 'y'); //once we've made it here once per session, we don't need to show splash again
|
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(doDaemonReady())
|
ReactDOM.render(
|
||||||
app.store.dispatch(doChangePath('/discover'))
|
<Provider store={store}>
|
||||||
app.store.dispatch(doFetchDaemonSettings())
|
<div><AuthOverlay /><App /><SnackBar /></div>
|
||||||
app.store.dispatch(doFileList())
|
</Provider>,
|
||||||
|
canvas
|
||||||
ReactDOM.render(<Provider store={store}><div>{ lbryio.enabled ? <AuthOverlay/> : '' }<App /><SnackBar /></div></Provider>, canvas)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.sessionStorage.getItem('loaded') == 'y') {
|
if (window.sessionStorage.getItem("loaded") == "y") {
|
||||||
onDaemonReady();
|
onDaemonReady();
|
||||||
} else {
|
} else {
|
||||||
ReactDOM.render(<SplashScreen onLoadDone={onDaemonReady} />, canvas);
|
ReactDOM.render(<SplashScreen onLoadDone={onDaemonReady} />, canvas);
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue