reduxify pull request 325
This commit is contained in:
commit
d591886df5
144 changed files with 5872 additions and 2358 deletions
|
@ -1,5 +1,5 @@
|
||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 0.13.0
|
current_version = 0.14.3
|
||||||
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+))?
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -14,6 +14,7 @@
|
||||||
/app/node_modules
|
/app/node_modules
|
||||||
/build/venv
|
/build/venv
|
||||||
/lbry-app-venv
|
/lbry-app-venv
|
||||||
|
/lbry-app
|
||||||
/lbry-venv
|
/lbry-venv
|
||||||
/daemon/build
|
/daemon/build
|
||||||
/daemon/venv
|
/daemon/venv
|
||||||
|
@ -27,3 +28,4 @@ build/daemon.zip
|
||||||
.vimrc
|
.vimrc
|
||||||
|
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
ui/yarn.lock
|
||||||
|
|
94
CHANGELOG.md
94
CHANGELOG.md
|
@ -8,15 +8,19 @@ Web UI version numbers should always match the corresponding version of LBRY App
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
*
|
* Added a new component, `FormFieldPrice` which is now used in Publish and Settings
|
||||||
*
|
*
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
*
|
* Some form field refactoring as we progress towards form sanity.
|
||||||
|
* When an "Open" button is clicked on a show page, if the file fails to open, the app will try to open the file's folder.
|
||||||
*
|
*
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
*
|
* Tiles will no longer be blurry on hover (Windows only bug)
|
||||||
|
* Removed placeholder values from price selection form fields, which was causing confusion that these were real values (#426)
|
||||||
|
* Fixed showing "other currency" help tip in publish form, which was caused due to not "setting" state for price
|
||||||
|
* Now using setState in formFieldPrice
|
||||||
*
|
*
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
@ -24,8 +28,90 @@ Web UI version numbers should always match the corresponding version of LBRY App
|
||||||
*
|
*
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
* Removed the label "Max Purchase Price" from settings page. It was redundant.
|
||||||
|
* Unused old files from previous commit(9c3d633)
|
||||||
*
|
*
|
||||||
*
|
|
||||||
|
## [0.14.3] - 2017-08-03
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Add tooltips to controls in header
|
||||||
|
* New flow for rewards authentication failure
|
||||||
|
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Make it clearer how to skip identity verification and add link to FAQ
|
||||||
|
* Reward-eligible content icon is now a rocket ship :D :D :D
|
||||||
|
* Change install description shown by operating systems
|
||||||
|
* Improved flow for when app is run with incompatible daemon
|
||||||
|
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Corrected improper pluralization on loading screen
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.14.2] - 2017-07-30
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Replaced horizontal scrollbars with scroll arrows
|
||||||
|
* Featured weekly reward content shows with an orange star
|
||||||
|
* Added pagination to channel pages
|
||||||
|
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Fixed requirement to double click play button on many videos
|
||||||
|
* Fixed errors from calls to `get` not bubbling correctly
|
||||||
|
* Fixed some corner-case flows that could break file pages
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.14.1] - 2017-07-28
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Fixed upgrade file path missing file name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [0.14.0] - 2017-07-28
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Identity verification for new reward participants
|
||||||
|
* Support rich markup in publishing descriptions and show pages.
|
||||||
|
* Release past publishing claims (and recover LBC) via the UI
|
||||||
|
* Added transition to card hovers to smooth animation
|
||||||
|
* Use randomly colored tiles when image is missing from metadata
|
||||||
|
* Added a loading message to file actions
|
||||||
|
* URL is auto suggested in Publish Page
|
||||||
|
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Publishing revamped. Editing claims is much easier.
|
||||||
|
* Daemon updated from v0.13.1 to [v0.14.2](https://github.com/lbryio/lbry/releases/tag/v0.14.2)
|
||||||
|
* Publish page now use `claim_list` rather than `file_list`
|
||||||
|
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* Removed bandwidth caps from settings, because the daemon was not respecting them anyway.
|
||||||
|
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Fixed bug with download notice when switching window focus
|
||||||
|
* Fixed newly published files appearing twice
|
||||||
|
* Fixed unconfirmed published files missing channel name
|
||||||
|
* Fixed old files from updated published claims appearing in downloaded list
|
||||||
|
* Fixed inappropriate text showing on searches
|
||||||
|
* Stop discover page from pushing jumping vertically while loading
|
||||||
|
* Restored feedback on claim amounts
|
||||||
|
* Fixed hiding price input when Free is checked on publish form
|
||||||
|
* Fixed hiding new identity fields on publish form
|
||||||
|
* Fixed files on downloaded tab not showing download progress
|
||||||
|
* Fixed downloading files that are deleted not being removed from the downloading list
|
||||||
|
* Fixed download progress bar not being cleared when a downloading file is deleted
|
||||||
|
* Fixed refresh regression after adding scroll position to history state
|
||||||
|
* Fixed app not monitoring download progress on files in progress between restarts
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [0.13.0] - 2017-06-30
|
## [0.13.0] - 2017-06-30
|
||||||
|
|
||||||
|
|
11
app/main.js
11
app/main.js
|
@ -27,7 +27,7 @@ const {version: localVersion} = require(app.getAppPath() + '/package.json');
|
||||||
|
|
||||||
const VERSION_CHECK_INTERVAL = 30 * 60 * 1000;
|
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';
|
||||||
|
const DAEMON_PATH = process.env.LBRY_DAEMON || path.join(__dirname, 'dist', 'lbrynet-daemon');
|
||||||
|
|
||||||
let client = jayson.client.http({
|
let client = jayson.client.http({
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
|
@ -207,13 +207,8 @@ function handleDaemonSubprocessExited() {
|
||||||
function launchDaemon() {
|
function launchDaemon() {
|
||||||
assert(!daemonSubprocess, 'Tried to launch daemon twice');
|
assert(!daemonSubprocess, 'Tried to launch daemon twice');
|
||||||
|
|
||||||
if (process.env.LBRY_DAEMON) {
|
console.log('Launching daemon:', DAEMON_PATH)
|
||||||
executable = process.env.LBRY_DAEMON;
|
daemonSubprocess = child_process.spawn(DAEMON_PATH)
|
||||||
} else {
|
|
||||||
executable = path.join(__dirname, 'dist', 'lbrynet-daemon');
|
|
||||||
}
|
|
||||||
console.log('Launching daemon:', executable)
|
|
||||||
daemonSubprocess = child_process.spawn(executable)
|
|
||||||
// Need to handle the data event instead of attaching to
|
// Need to handle the data event instead of attaching to
|
||||||
// process.stdout because the latter doesn't work. I believe on
|
// process.stdout because the latter doesn't work. I believe on
|
||||||
// windows it buffers stdout and we don't get any meaningful output
|
// windows it buffers stdout and we don't get any meaningful output
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "LBRY",
|
"name": "LBRY",
|
||||||
"version": "0.13.0",
|
"version": "0.14.3",
|
||||||
"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": "A browser for the LBRY network, a digital marketplace controlled by its users.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "LBRY Inc.",
|
"name": "LBRY Inc.",
|
||||||
"email": "hello@lbry.io"
|
"email": "hello@lbry.io"
|
||||||
|
@ -18,5 +18,8 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron-rebuild": "^1.5.11"
|
"electron-rebuild": "^1.5.11"
|
||||||
|
},
|
||||||
|
"lbrySettings": {
|
||||||
|
"lbrynetDaemonVersion": "0.14.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1 +1 @@
|
||||||
https://github.com/lbryio/lbry/releases/download/v0.13.1/lbrynet-daemon-v0.13.1-OSNAME.zip
|
https://github.com/lbryio/lbry/releases/download/v0.14.2/lbrynet-daemon-v0.14.2-OSNAME.zip
|
|
@ -46,8 +46,8 @@ dir dist # verify that binary was built/named correctly
|
||||||
|
|
||||||
# sign binary
|
# sign binary
|
||||||
nuget install secure-file -ExcludeVersion
|
nuget install secure-file -ExcludeVersion
|
||||||
secure-file\tools\secure-file -decrypt build\lbry2.pfx.enc -secret "$env:pfx_key"
|
secure-file\tools\secure-file -decrypt build\lbry3.pfx.enc -secret "$env:pfx_key"
|
||||||
& ${env:SIGNTOOL_PATH} sign /f build\lbry2.pfx /p "$env:key_pass" /tr http://tsa.starfieldtech.com /td SHA256 /fd SHA256 dist\*.exe
|
& ${env:SIGNTOOL_PATH} sign /f build\lbry3.pfx /p "$env:key_pass" /tr http://tsa.starfieldtech.com /td SHA256 /fd SHA256 dist\*.exe
|
||||||
|
|
||||||
|
|
||||||
python build\upload_assets.py
|
python build\upload_assets.py
|
|
@ -79,11 +79,14 @@ if $OSX; then
|
||||||
else
|
else
|
||||||
OSNAME="linux"
|
OSNAME="linux"
|
||||||
fi
|
fi
|
||||||
DAEMON_URL="$(cat "$BUILD_DIR/DAEMON_URL" | sed "s/OSNAME/${OSNAME}/")"
|
DAEMON_VER=$(node -e "console.log(require(\"$ROOT/app/package.json\").lbrySettings.lbrynetDaemonVersion)")
|
||||||
|
DAEMON_URL="https://github.com/lbryio/lbry/releases/download/v${DAEMON_VER}/lbrynet-daemon-v${DAEMON_VER}-${OSNAME}.zip"
|
||||||
wget --quiet "$DAEMON_URL" -O "$BUILD_DIR/daemon.zip"
|
wget --quiet "$DAEMON_URL" -O "$BUILD_DIR/daemon.zip"
|
||||||
unzip "$BUILD_DIR/daemon.zip" -d "$ROOT/app/dist/"
|
unzip "$BUILD_DIR/daemon.zip" -d "$ROOT/app/dist/"
|
||||||
rm "$BUILD_DIR/daemon.zip"
|
rm "$BUILD_DIR/daemon.zip"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###################
|
###################
|
||||||
# Build the app #
|
# Build the app #
|
||||||
###################
|
###################
|
||||||
|
|
Binary file not shown.
BIN
build/lbry3.pfx.enc
Normal file
BIN
build/lbry3.pfx.enc
Normal file
Binary file not shown.
|
@ -57,8 +57,8 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"devtron": "^1.4.0",
|
"devtron": "^1.4.0",
|
||||||
"electron": "^1.4.15",
|
"electron": "^1.7.5",
|
||||||
"electron-builder": "^11.7.0",
|
"electron-builder": "^11.7.0",
|
||||||
"electron-debug": "^1.1.0"
|
"electron-debug": "^1.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,31 +13,43 @@ import { doSearch } from "actions/search";
|
||||||
import { doFetchDaemonSettings } from "actions/settings";
|
import { doFetchDaemonSettings } from "actions/settings";
|
||||||
import { doAuthenticate } from "actions/user";
|
import { doAuthenticate } from "actions/user";
|
||||||
import { doFileList } from "actions/file_info";
|
import { doFileList } from "actions/file_info";
|
||||||
|
import { toQueryString } from "util/query_params";
|
||||||
|
import { parseQueryParams } from "util/query_params";
|
||||||
|
|
||||||
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 { download } = remote.require("electron-dl");
|
const { download } = remote.require("electron-dl");
|
||||||
const fs = remote.require("fs");
|
const fs = remote.require("fs");
|
||||||
|
const { lbrySettings: config } = require("../../../app/package.json");
|
||||||
|
|
||||||
const queryStringFromParams = params => {
|
export function doNavigate(path, params = {}, options = {}) {
|
||||||
return Object.keys(params).map(key => `${key}=${params[key]}`).join("&");
|
|
||||||
};
|
|
||||||
|
|
||||||
export function doNavigate(path, params = {}) {
|
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
let url = path;
|
let url = path;
|
||||||
if (params) url = `${url}?${queryStringFromParams(params)}`;
|
if (params) url = `${url}?${toQueryString(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 doAuthNavigate(pathAfterAuth = null, params = {}) {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
if (pathAfterAuth) {
|
||||||
|
dispatch({
|
||||||
|
type: types.CHANGE_AFTER_AUTH_PATH,
|
||||||
|
data: {
|
||||||
|
path: `${pathAfterAuth}?${toQueryString(params)}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
dispatch(doNavigate("/auth"));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doChangePath(path, options = {}) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.CHANGE_PATH,
|
type: types.CHANGE_PATH,
|
||||||
|
@ -48,8 +60,12 @@ export function doChangePath(path) {
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const pageTitle = selectPageTitle(state);
|
const pageTitle = selectPageTitle(state);
|
||||||
|
const scrollY = options.scrollY;
|
||||||
|
|
||||||
window.document.title = pageTitle;
|
window.document.title = pageTitle;
|
||||||
window.scrollTo(0, 0);
|
|
||||||
|
if (scrollY) window.scrollTo(0, scrollY);
|
||||||
|
else window.scrollTo(0, 0);
|
||||||
|
|
||||||
const currentPage = selectCurrentPage(state);
|
const currentPage = selectCurrentPage(state);
|
||||||
if (currentPage === "search") {
|
if (currentPage === "search") {
|
||||||
|
@ -62,15 +78,32 @@ export function doChangePath(path) {
|
||||||
export function doHistoryBack() {
|
export function doHistoryBack() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
if (!history.state) return;
|
if (!history.state) return;
|
||||||
|
if (history.state.index === 0) return;
|
||||||
|
|
||||||
history.back();
|
history.back();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doHistoryPush(params, title, relativeUrl) {
|
export function doHistoryPush(currentState, title, relativeUrl) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
title += " - LBRY";
|
title += " - LBRY";
|
||||||
history.pushState(params, title, `#${relativeUrl}`);
|
history.pushState(currentState, title, `#${relativeUrl}`);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doRecordScroll(scroll) {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
const state = getState();
|
||||||
|
const historyState = history.state;
|
||||||
|
|
||||||
|
if (!historyState) return;
|
||||||
|
|
||||||
|
historyState.scrollY = scroll;
|
||||||
|
history.replaceState(
|
||||||
|
historyState,
|
||||||
|
document.title,
|
||||||
|
`#${state.app.currentPath}`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,8 +150,10 @@ 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(
|
||||||
const upgradeFilename = selectUpgradeFilename(state);
|
remote.app.getPath("temp") + require("path").sep
|
||||||
|
),
|
||||||
|
upgradeFilename = selectUpgradeFilename(state);
|
||||||
|
|
||||||
let options = {
|
let options = {
|
||||||
onProgress: p => dispatch(doUpdateDownloadProgress(Math.round(p * 100))),
|
onProgress: p => dispatch(doUpdateDownloadProgress(Math.round(p * 100))),
|
||||||
|
@ -202,11 +237,21 @@ export function doCheckUpgradeAvailable() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doCheckDaemonVersion() {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
lbry.version().then(({ lbrynet_version }) => {
|
||||||
|
dispatch({
|
||||||
|
type: config.lbrynetDaemonVersion == lbrynet_version
|
||||||
|
? types.DAEMON_VERSION_MATCH
|
||||||
|
: types.DAEMON_VERSION_MISMATCH,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
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(errorList);
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.OPEN_MODAL,
|
type: types.OPEN_MODAL,
|
||||||
data: {
|
data: {
|
||||||
|
@ -219,6 +264,9 @@ export function doAlertError(errorList) {
|
||||||
|
|
||||||
export function doDaemonReady() {
|
export function doDaemonReady() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
|
const path = window.location.hash || "#/discover";
|
||||||
|
const params = parseQueryParams(path.split("?")[1] || "");
|
||||||
|
history.replaceState({ params, index: 0 }, document.title, `${path}`);
|
||||||
dispatch(doAuthenticate());
|
dispatch(doAuthenticate());
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.DAEMON_READY,
|
type: types.DAEMON_READY,
|
||||||
|
@ -256,3 +304,9 @@ export function doChangeLanguage(newLanguage) {
|
||||||
data: { newLanguage: newLanguage },
|
data: { newLanguage: newLanguage },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doQuit() {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
remote.app.quit();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -5,17 +5,18 @@ import lbryuri from "lbryuri";
|
||||||
import { selectBalance } from "selectors/wallet";
|
import { selectBalance } from "selectors/wallet";
|
||||||
import {
|
import {
|
||||||
selectFileInfoForUri,
|
selectFileInfoForUri,
|
||||||
selectUrisDownloading,
|
selectDownloadingByOutpoint,
|
||||||
} from "selectors/file_info";
|
} from "selectors/file_info";
|
||||||
import { selectResolvingUris } from "selectors/content";
|
import { selectResolvingUris } from "selectors/content";
|
||||||
import { selectCostInfoForUri } from "selectors/cost_info";
|
import { selectCostInfoForUri } from "selectors/cost_info";
|
||||||
import { doOpenModal } from "actions/app";
|
import { doAlertError, doOpenModal } from "actions/app";
|
||||||
import { doClaimEligiblePurchaseRewards } from "actions/rewards";
|
import { doClaimEligiblePurchaseRewards } from "actions/rewards";
|
||||||
import { selectBadgeNumber } from "selectors/app";
|
import { selectBadgeNumber } from "selectors/app";
|
||||||
import { selectTotalDownloadProgress } from "selectors/file_info";
|
import { selectTotalDownloadProgress } from "selectors/file_info";
|
||||||
import setBadge from "util/setBadge";
|
import setBadge from "util/setBadge";
|
||||||
import setProgressBar from "util/setProgressBar";
|
import setProgressBar from "util/setProgressBar";
|
||||||
import batchActions from "util/batchActions";
|
import batchActions from "util/batchActions";
|
||||||
|
import * as modals from "constants/modal_types";
|
||||||
|
|
||||||
const { ipcRenderer } = require("electron");
|
const { ipcRenderer } = require("electron");
|
||||||
|
|
||||||
|
@ -132,6 +133,34 @@ export function doFetchFeaturedUris() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doFetchRewardedContent() {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
|
const success = nameToClaimId => {
|
||||||
|
dispatch({
|
||||||
|
type: types.FETCH_REWARD_CONTENT_COMPLETED,
|
||||||
|
data: {
|
||||||
|
claimIds: Object.values(nameToClaimId),
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const failure = () => {
|
||||||
|
dispatch({
|
||||||
|
type: types.FETCH_REWARD_CONTENT_COMPLETED,
|
||||||
|
data: {
|
||||||
|
claimIds: [],
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
lbryio.call("reward", "list_featured").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();
|
||||||
|
@ -198,24 +227,38 @@ export function doUpdateLoadStatus(uri, outpoint) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doStartDownload(uri, outpoint) {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
const state = getState();
|
||||||
|
|
||||||
|
if (!outpoint) {
|
||||||
|
throw new Error("outpoint is required to begin a download");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { downloadingByOutpoint = {} } = state.fileInfo;
|
||||||
|
|
||||||
|
if (downloadingByOutpoint[outpoint]) return;
|
||||||
|
|
||||||
|
lbry.file_list({ outpoint, full_status: true }).then(([fileInfo]) => {
|
||||||
|
dispatch({
|
||||||
|
type: types.DOWNLOADING_STARTED,
|
||||||
|
data: {
|
||||||
|
uri,
|
||||||
|
outpoint,
|
||||||
|
fileInfo,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(doUpdateLoadStatus(uri, outpoint));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
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
|
dispatch(doStartDownload(uri, streamInfo.outpoint));
|
||||||
.file_list({ outpoint: streamInfo.outpoint, full_status: true })
|
|
||||||
.then(([fileInfo]) => {
|
|
||||||
dispatch({
|
|
||||||
type: types.DOWNLOADING_STARTED,
|
|
||||||
data: {
|
|
||||||
uri,
|
|
||||||
outpoint: streamInfo.outpoint,
|
|
||||||
fileInfo,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(doUpdateLoadStatus(uri, streamInfo.outpoint));
|
|
||||||
});
|
|
||||||
|
|
||||||
lbryio
|
lbryio
|
||||||
.call("file", "view", {
|
.call("file", "view", {
|
||||||
|
@ -240,22 +283,32 @@ export function doLoadVideo(uri) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
lbry.get({ uri }).then(streamInfo => {
|
lbry
|
||||||
const timeout =
|
.get({ uri })
|
||||||
streamInfo === null ||
|
.then(streamInfo => {
|
||||||
typeof streamInfo !== "object" ||
|
const timeout =
|
||||||
streamInfo.error == "Timeout";
|
streamInfo === null ||
|
||||||
|
typeof streamInfo !== "object" ||
|
||||||
|
streamInfo.error == "Timeout";
|
||||||
|
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
|
dispatch({
|
||||||
|
type: types.LOADING_VIDEO_FAILED,
|
||||||
|
data: { uri },
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(doOpenModal("timedOut"));
|
||||||
|
} else {
|
||||||
|
dispatch(doDownloadFile(uri, streamInfo));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.LOADING_VIDEO_FAILED,
|
type: types.LOADING_VIDEO_FAILED,
|
||||||
data: { uri },
|
data: { uri },
|
||||||
});
|
});
|
||||||
dispatch(doOpenModal("timedOut"));
|
dispatch(doAlertError(error));
|
||||||
} else {
|
});
|
||||||
dispatch(doDownloadFile(uri, streamInfo));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,8 +317,9 @@ export function doPurchaseUri(uri, purchaseModalName) {
|
||||||
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 downloadingByOutpoint = selectDownloadingByOutpoint(state);
|
||||||
const alreadyDownloading = !!downloadingByUri[uri];
|
const alreadyDownloading =
|
||||||
|
fileInfo && !!downloadingByOutpoint[fileInfo.outpoint];
|
||||||
|
|
||||||
// we already fully downloaded the file.
|
// we already fully downloaded the file.
|
||||||
if (fileInfo && fileInfo.completed) {
|
if (fileInfo && fileInfo.completed) {
|
||||||
|
@ -292,7 +346,7 @@ export function doPurchaseUri(uri, purchaseModalName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cost > balance) {
|
if (cost > balance) {
|
||||||
dispatch(doOpenModal("notEnoughCredits"));
|
dispatch(doOpenModal(modals.INSUFFICIENT_CREDITS));
|
||||||
} else {
|
} else {
|
||||||
dispatch(doOpenModal(purchaseModalName));
|
dispatch(doOpenModal(purchaseModalName));
|
||||||
}
|
}
|
||||||
|
@ -301,22 +355,28 @@ export function doPurchaseUri(uri, purchaseModalName) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doFetchClaimsByChannel(uri, page = 1) {
|
export function doFetchClaimsByChannel(uri, page) {
|
||||||
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, page },
|
||||||
});
|
});
|
||||||
|
|
||||||
lbry.claim_list_by_channel({ uri, page }).then(result => {
|
lbry.claim_list_by_channel({ uri, page }).then(result => {
|
||||||
const claimResult = result[uri],
|
const claimResult = result[uri],
|
||||||
claims = claimResult ? claimResult.claims_in_channel : [];
|
claims = claimResult ? claimResult.claims_in_channel : [],
|
||||||
|
totalPages = claimResult
|
||||||
|
? claimResult.claims_in_channel_pages
|
||||||
|
: undefined,
|
||||||
|
currentPage = claimResult ? claimResult.returned_page : undefined;
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.FETCH_CHANNEL_CLAIMS_COMPLETED,
|
type: types.FETCH_CHANNEL_CLAIMS_COMPLETED,
|
||||||
data: {
|
data: {
|
||||||
uri,
|
uri,
|
||||||
claims: claims,
|
claims,
|
||||||
|
totalPages,
|
||||||
|
page: currentPage,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -339,3 +399,68 @@ export function doFetchClaimListMine() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doFetchChannelListMine() {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
dispatch({
|
||||||
|
type: types.FETCH_CHANNEL_LIST_MINE_STARTED,
|
||||||
|
});
|
||||||
|
|
||||||
|
const callback = channels => {
|
||||||
|
dispatch({
|
||||||
|
type: types.FETCH_CHANNEL_LIST_MINE_COMPLETED,
|
||||||
|
data: { claims: channels },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
lbry.channel_list_mine().then(callback);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doCreateChannel(name, amount) {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
dispatch({
|
||||||
|
type: types.CREATE_CHANNEL_STARTED,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
lbry
|
||||||
|
.channel_new({
|
||||||
|
channel_name: name,
|
||||||
|
amount: parseFloat(amount),
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
channelClaim => {
|
||||||
|
channelClaim.name = name;
|
||||||
|
dispatch({
|
||||||
|
type: types.CREATE_CHANNEL_COMPLETED,
|
||||||
|
data: { channelClaim },
|
||||||
|
});
|
||||||
|
resolve(channelClaim);
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doPublish(params) {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const success = claim => {
|
||||||
|
resolve(claim);
|
||||||
|
|
||||||
|
if (claim === true) dispatch(doFetchClaimListMine());
|
||||||
|
else
|
||||||
|
setTimeout(() => dispatch(doFetchClaimListMine()), 20000, {
|
||||||
|
once: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const failure = err => reject(err);
|
||||||
|
|
||||||
|
lbry.publishDeprecated(params, null, success, failure);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -3,15 +3,18 @@ import lbry from "lbry";
|
||||||
import { doFetchClaimListMine } from "actions/content";
|
import { doFetchClaimListMine } from "actions/content";
|
||||||
import {
|
import {
|
||||||
selectClaimsByUri,
|
selectClaimsByUri,
|
||||||
selectClaimListMineIsPending,
|
selectIsFetchingClaimListMine,
|
||||||
selectMyClaimsOutpoints,
|
selectMyClaimsOutpoints,
|
||||||
} from "selectors/claims";
|
} from "selectors/claims";
|
||||||
import {
|
import {
|
||||||
selectFileListIsPending,
|
selectIsFetchingFileList,
|
||||||
selectFileInfosByOutpoint,
|
selectFileInfosByOutpoint,
|
||||||
selectUrisLoading,
|
selectUrisLoading,
|
||||||
|
selectTotalDownloadProgress,
|
||||||
} from "selectors/file_info";
|
} from "selectors/file_info";
|
||||||
import { doCloseModal } from "actions/app";
|
import { doCloseModal, doHistoryBack } from "actions/app";
|
||||||
|
import setProgressBar from "util/setProgressBar";
|
||||||
|
import batchActions from "util/batchActions";
|
||||||
|
|
||||||
const { shell } = require("electron");
|
const { shell } = require("electron");
|
||||||
|
|
||||||
|
@ -48,16 +51,16 @@ export function doFetchFileInfo(uri) {
|
||||||
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 isFetching = selectIsFetchingFileList(state);
|
||||||
|
|
||||||
if (!isPending) {
|
if (!isFetching) {
|
||||||
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_SUCCEEDED,
|
||||||
data: {
|
data: {
|
||||||
fileInfos,
|
fileInfos,
|
||||||
},
|
},
|
||||||
|
@ -69,7 +72,10 @@ export function doFileList() {
|
||||||
|
|
||||||
export function doOpenFileInShell(fileInfo) {
|
export function doOpenFileInShell(fileInfo) {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
shell.openItem(fileInfo.download_path);
|
const success = shell.openItem(fileInfo.download_path);
|
||||||
|
if (!success) {
|
||||||
|
dispatch(doOpenFileInFolder(fileInfo));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,14 +108,12 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const success = () => {
|
const success = dispatch({
|
||||||
dispatch({
|
type: types.ABANDON_CLAIM_SUCCEEDED,
|
||||||
type: types.ABANDON_CLAIM_COMPLETED,
|
data: {
|
||||||
data: {
|
claimId: fileInfo.claim_id,
|
||||||
claimId: fileInfo.claim_id,
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
};
|
|
||||||
lbry.claim_abandon({ claim_id: fileInfo.claim_id }).then(success);
|
lbry.claim_abandon({ claim_id: fileInfo.claim_id }).then(success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,17 +125,32 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(doCloseModal());
|
const totalProgress = selectTotalDownloadProgress(getState());
|
||||||
|
setProgressBar(totalProgress);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doDeleteFileAndGoBack(
|
||||||
|
fileInfo,
|
||||||
|
deleteFromComputer,
|
||||||
|
abandonClaim
|
||||||
|
) {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
const actions = [];
|
||||||
|
actions.push(doCloseModal());
|
||||||
|
actions.push(doHistoryBack());
|
||||||
|
actions.push(doDeleteFile(fileInfo, deleteFromComputer, abandonClaim));
|
||||||
|
dispatch(batchActions(...actions));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doFetchFileInfosAndPublishedClaims() {
|
export function doFetchFileInfosAndPublishedClaims() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
const state = getState(),
|
const state = getState(),
|
||||||
isClaimListMinePending = selectClaimListMineIsPending(state),
|
isFetchingClaimListMine = selectIsFetchingClaimListMine(state),
|
||||||
isFileInfoListPending = selectFileListIsPending(state);
|
isFetchingFileInfo = selectIsFetchingFileList(state);
|
||||||
|
|
||||||
dispatch(doFetchClaimListMine());
|
if (!isFetchingClaimListMine) dispatch(doFetchClaimListMine());
|
||||||
dispatch(doFileList());
|
if (!isFetchingFileInfo) dispatch(doFileList());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as types from "constants/action_types";
|
import * as types from "constants/action_types";
|
||||||
|
import * as modals from "constants/modal_types";
|
||||||
import lbryio from "lbryio";
|
import lbryio from "lbryio";
|
||||||
import rewards from "rewards";
|
import rewards from "rewards";
|
||||||
import { selectRewardsByType } from "selectors/rewards";
|
import { selectRewardsByType } from "selectors/rewards";
|
||||||
|
@ -58,6 +59,12 @@ export function doClaimReward(reward, saveError = false) {
|
||||||
reward,
|
reward,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (reward.reward_type == rewards.TYPE_NEW_USER) {
|
||||||
|
dispatch({
|
||||||
|
type: types.OPEN_MODAL,
|
||||||
|
data: { modal: modals.FIRST_REWARD },
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const failure = error => {
|
const failure = error => {
|
||||||
|
@ -99,9 +106,7 @@ export function doClaimEligiblePurchaseRewards() {
|
||||||
if (unclaimedType) {
|
if (unclaimedType) {
|
||||||
dispatch(doClaimRewardType(unclaimedType));
|
dispatch(doClaimRewardType(unclaimedType));
|
||||||
}
|
}
|
||||||
if (types[rewards.TYPE_FEATURED_DOWNLOAD] === false) {
|
dispatch(doClaimRewardType(rewards.TYPE_FEATURED_DOWNLOAD));
|
||||||
dispatch(doClaimRewardType(rewards.TYPE_FEATURED_DOWNLOAD));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import * as types from "constants/action_types";
|
import * as types from "constants/action_types";
|
||||||
|
import batchActions from "util/batchActions";
|
||||||
import lbry from "lbry";
|
import lbry from "lbry";
|
||||||
|
import fs from "fs";
|
||||||
|
import http from "http";
|
||||||
|
|
||||||
export function doFetchDaemonSettings() {
|
export function doFetchDaemonSettings() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
|
@ -41,3 +44,296 @@ export function doSetClientSetting(key, value) {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doResolveLanguage(locale) {
|
||||||
|
const langs = {
|
||||||
|
aa: ["Afar", "Afar"],
|
||||||
|
ab: ["Abkhazian", "Аҧсуа"],
|
||||||
|
af: ["Afrikaans", "Afrikaans"],
|
||||||
|
ak: ["Akan", "Akana"],
|
||||||
|
am: ["Amharic", "አማርኛ"],
|
||||||
|
an: ["Aragonese", "Aragonés"],
|
||||||
|
ar: ["Arabic", "العربية"],
|
||||||
|
as: ["Assamese", "অসমীয়া"],
|
||||||
|
av: ["Avar", "Авар"],
|
||||||
|
ay: ["Aymara", "Aymar"],
|
||||||
|
az: ["Azerbaijani", "Azərbaycanca / آذربايجان"],
|
||||||
|
ba: ["Bashkir", "Башҡорт"],
|
||||||
|
be: ["Belarusian", "Беларуская"],
|
||||||
|
bg: ["Bulgarian", "Български"],
|
||||||
|
bh: ["Bihari", "भोजपुरी"],
|
||||||
|
bi: ["Bislama", "Bislama"],
|
||||||
|
bm: ["Bambara", "Bamanankan"],
|
||||||
|
bn: ["Bengali", "বাংলা"],
|
||||||
|
bo: ["Tibetan", "བོད་ཡིག / Bod skad"],
|
||||||
|
br: ["Breton", "Brezhoneg"],
|
||||||
|
bs: ["Bosnian", "Bosanski"],
|
||||||
|
ca: ["Catalan", "Català"],
|
||||||
|
ce: ["Chechen", "Нохчийн"],
|
||||||
|
ch: ["Chamorro", "Chamoru"],
|
||||||
|
co: ["Corsican", "Corsu"],
|
||||||
|
cr: ["Cree", "Nehiyaw"],
|
||||||
|
cs: ["Czech", "Česky"],
|
||||||
|
cu: ["Old Church Slavonic / Old Bulgarian", "словѣньскъ / slověnĭskŭ"],
|
||||||
|
cv: ["Chuvash", "Чăваш"],
|
||||||
|
cy: ["Welsh", "Cymraeg"],
|
||||||
|
da: ["Danish", "Dansk"],
|
||||||
|
de: ["German", "Deutsch"],
|
||||||
|
dv: ["Divehi", "ދިވެހިބަސް"],
|
||||||
|
dz: ["Dzongkha", "ཇོང་ཁ"],
|
||||||
|
ee: ["Ewe", "Ɛʋɛ"],
|
||||||
|
el: ["Greek", "Ελληνικά"],
|
||||||
|
en: ["English", "English"],
|
||||||
|
eo: ["Esperanto", "Esperanto"],
|
||||||
|
es: ["Spanish", "Español"],
|
||||||
|
et: ["Estonian", "Eesti"],
|
||||||
|
eu: ["Basque", "Euskara"],
|
||||||
|
fa: ["Persian", "فارسی"],
|
||||||
|
ff: ["Peul", "Fulfulde"],
|
||||||
|
fi: ["Finnish", "Suomi"],
|
||||||
|
fj: ["Fijian", "Na Vosa Vakaviti"],
|
||||||
|
fo: ["Faroese", "Føroyskt"],
|
||||||
|
fr: ["French", "Français"],
|
||||||
|
fy: ["West Frisian", "Frysk"],
|
||||||
|
ga: ["Irish", "Gaeilge"],
|
||||||
|
gd: ["Scottish Gaelic", "Gàidhlig"],
|
||||||
|
gl: ["Galician", "Galego"],
|
||||||
|
gn: ["Guarani", "Avañe'ẽ"],
|
||||||
|
gu: ["Gujarati", "ગુજરાતી"],
|
||||||
|
gv: ["Manx", "Gaelg"],
|
||||||
|
ha: ["Hausa", "هَوُسَ"],
|
||||||
|
he: ["Hebrew", "עברית"],
|
||||||
|
hi: ["Hindi", "हिन्दी"],
|
||||||
|
ho: ["Hiri Motu", "Hiri Motu"],
|
||||||
|
hr: ["Croatian", "Hrvatski"],
|
||||||
|
ht: ["Haitian", "Krèyol ayisyen"],
|
||||||
|
hu: ["Hungarian", "Magyar"],
|
||||||
|
hy: ["Armenian", "Հայերեն"],
|
||||||
|
hz: ["Herero", "Otsiherero"],
|
||||||
|
ia: ["Interlingua", "Interlingua"],
|
||||||
|
id: ["Indonesian", "Bahasa Indonesia"],
|
||||||
|
ie: ["Interlingue", "Interlingue"],
|
||||||
|
ig: ["Igbo", "Igbo"],
|
||||||
|
ii: ["Sichuan Yi", "ꆇꉙ / 四川彝语"],
|
||||||
|
ik: ["Inupiak", "Iñupiak"],
|
||||||
|
io: ["Ido", "Ido"],
|
||||||
|
is: ["Icelandic", "Íslenska"],
|
||||||
|
it: ["Italian", "Italiano"],
|
||||||
|
iu: ["Inuktitut", "ᐃᓄᒃᑎᑐᑦ"],
|
||||||
|
ja: ["Japanese", "日本語"],
|
||||||
|
jv: ["Javanese", "Basa Jawa"],
|
||||||
|
ka: ["Georgian", "ქართული"],
|
||||||
|
kg: ["Kongo", "KiKongo"],
|
||||||
|
ki: ["Kikuyu", "Gĩkũyũ"],
|
||||||
|
kj: ["Kuanyama", "Kuanyama"],
|
||||||
|
kk: ["Kazakh", "Қазақша"],
|
||||||
|
kl: ["Greenlandic", "Kalaallisut"],
|
||||||
|
km: ["Cambodian", "ភាសាខ្មែរ"],
|
||||||
|
kn: ["Kannada", "ಕನ್ನಡ"],
|
||||||
|
ko: ["Korean", "한국어"],
|
||||||
|
kr: ["Kanuri", "Kanuri"],
|
||||||
|
ks: ["Kashmiri", "कश्मीरी / كشميري"],
|
||||||
|
ku: ["Kurdish", "Kurdî / كوردی"],
|
||||||
|
kv: ["Komi", "Коми"],
|
||||||
|
kw: ["Cornish", "Kernewek"],
|
||||||
|
ky: ["Kirghiz", "Kırgızca / Кыргызча"],
|
||||||
|
la: ["Latin", "Latina"],
|
||||||
|
lb: ["Luxembourgish", "Lëtzebuergesch"],
|
||||||
|
lg: ["Ganda", "Luganda"],
|
||||||
|
li: ["Limburgian", "Limburgs"],
|
||||||
|
ln: ["Lingala", "Lingála"],
|
||||||
|
lo: ["Laotian", "ລາວ / Pha xa lao"],
|
||||||
|
lt: ["Lithuanian", "Lietuvių"],
|
||||||
|
lv: ["Latvian", "Latviešu"],
|
||||||
|
mg: ["Malagasy", "Malagasy"],
|
||||||
|
mh: ["Marshallese", "Kajin Majel / Ebon"],
|
||||||
|
mi: ["Maori", "Māori"],
|
||||||
|
mk: ["Macedonian", "Македонски"],
|
||||||
|
ml: ["Malayalam", "മലയാളം"],
|
||||||
|
mn: ["Mongolian", "Монгол"],
|
||||||
|
mo: ["Moldovan", "Moldovenească"],
|
||||||
|
mr: ["Marathi", "मराठी"],
|
||||||
|
ms: ["Malay", "Bahasa Melayu"],
|
||||||
|
mt: ["Maltese", "bil-Malti"],
|
||||||
|
my: ["Burmese", "Myanmasa"],
|
||||||
|
na: ["Nauruan", "Dorerin Naoero"],
|
||||||
|
nd: ["North Ndebele", "Sindebele"],
|
||||||
|
ne: ["Nepali", "नेपाली"],
|
||||||
|
ng: ["Ndonga", "Oshiwambo"],
|
||||||
|
nl: ["Dutch", "Nederlands"],
|
||||||
|
nn: ["Norwegian Nynorsk", "Norsk (nynorsk)"],
|
||||||
|
no: ["Norwegian", "Norsk (bokmål / riksmål)"],
|
||||||
|
nr: ["South Ndebele", "isiNdebele"],
|
||||||
|
nv: ["Navajo", "Diné bizaad"],
|
||||||
|
ny: ["Chichewa", "Chi-Chewa"],
|
||||||
|
oc: ["Occitan", "Occitan"],
|
||||||
|
oj: ["Ojibwa", "ᐊᓂᔑᓈᐯᒧᐎᓐ / Anishinaabemowin"],
|
||||||
|
om: ["Oromo", "Oromoo"],
|
||||||
|
or: ["Oriya", "ଓଡ଼ିଆ"],
|
||||||
|
os: ["Ossetian / Ossetic", "Иронау"],
|
||||||
|
pa: ["Panjabi / Punjabi", "ਪੰਜਾਬੀ / पंजाबी / پنجابي"],
|
||||||
|
pi: ["Pali", "Pāli / पाऴि"],
|
||||||
|
pl: ["Polish", "Polski"],
|
||||||
|
ps: ["Pashto", "پښتو"],
|
||||||
|
pt: ["Portuguese", "Português"],
|
||||||
|
qu: ["Quechua", "Runa Simi"],
|
||||||
|
rm: ["Raeto Romance", "Rumantsch"],
|
||||||
|
rn: ["Kirundi", "Kirundi"],
|
||||||
|
ro: ["Romanian", "Română"],
|
||||||
|
ru: ["Russian", "Русский"],
|
||||||
|
rw: ["Rwandi", "Kinyarwandi"],
|
||||||
|
sa: ["Sanskrit", "संस्कृतम्"],
|
||||||
|
sc: ["Sardinian", "Sardu"],
|
||||||
|
sd: ["Sindhi", "सिनधि"],
|
||||||
|
se: ["Northern Sami", "Sámegiella"],
|
||||||
|
sg: ["Sango", "Sängö"],
|
||||||
|
sh: ["Serbo-Croatian", "Srpskohrvatski / Српскохрватски"],
|
||||||
|
si: ["Sinhalese", "සිංහල"],
|
||||||
|
sk: ["Slovak", "Slovenčina"],
|
||||||
|
sl: ["Slovenian", "Slovenščina"],
|
||||||
|
sm: ["Samoan", "Gagana Samoa"],
|
||||||
|
sn: ["Shona", "chiShona"],
|
||||||
|
so: ["Somalia", "Soomaaliga"],
|
||||||
|
sq: ["Albanian", "Shqip"],
|
||||||
|
sr: ["Serbian", "Српски"],
|
||||||
|
ss: ["Swati", "SiSwati"],
|
||||||
|
st: ["Southern Sotho", "Sesotho"],
|
||||||
|
su: ["Sundanese", "Basa Sunda"],
|
||||||
|
sv: ["Swedish", "Svenska"],
|
||||||
|
sw: ["Swahili", "Kiswahili"],
|
||||||
|
ta: ["Tamil", "தமிழ்"],
|
||||||
|
te: ["Telugu", "తెలుగు"],
|
||||||
|
tg: ["Tajik", "Тоҷикӣ"],
|
||||||
|
th: ["Thai", "ไทย / Phasa Thai"],
|
||||||
|
ti: ["Tigrinya", "ትግርኛ"],
|
||||||
|
tk: ["Turkmen", "Туркмен / تركمن"],
|
||||||
|
tl: ["Tagalog / Filipino", "Tagalog"],
|
||||||
|
tn: ["Tswana", "Setswana"],
|
||||||
|
to: ["Tonga", "Lea Faka-Tonga"],
|
||||||
|
tr: ["Turkish", "Türkçe"],
|
||||||
|
ts: ["Tsonga", "Xitsonga"],
|
||||||
|
tt: ["Tatar", "Tatarça"],
|
||||||
|
tw: ["Twi", "Twi"],
|
||||||
|
ty: ["Tahitian", "Reo Mā`ohi"],
|
||||||
|
ug: ["Uyghur", "Uyƣurqə / ئۇيغۇرچە"],
|
||||||
|
uk: ["Ukrainian", "Українська"],
|
||||||
|
ur: ["Urdu", "اردو"],
|
||||||
|
uz: ["Uzbek", "Ўзбек"],
|
||||||
|
ve: ["Venda", "Tshivenḓa"],
|
||||||
|
vi: ["Vietnamese", "Tiếng Việt"],
|
||||||
|
vo: ["Volapük", "Volapük"],
|
||||||
|
wa: ["Walloon", "Walon"],
|
||||||
|
wo: ["Wolof", "Wollof"],
|
||||||
|
xh: ["Xhosa", "isiXhosa"],
|
||||||
|
yi: ["Yiddish", "ייִדיש"],
|
||||||
|
yo: ["Yoruba", "Yorùbá"],
|
||||||
|
za: ["Zhuang", "Cuengh / Tôô / 壮语"],
|
||||||
|
zh: ["Chinese", "中文"],
|
||||||
|
zu: ["Zulu", "isiZulu"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const lang = locale.substring(0, 2);
|
||||||
|
return {
|
||||||
|
type: types.LANGUAGE_RESOLVED,
|
||||||
|
data: { key: locale, value: `${langs[lang][0]} (${langs[lang][1]})` },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doDownloadLanguage(lang, destinationPath) {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
const plainLanguage = lang.replace(".json", "");
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: types.DOWNLOAD_LANGUAGE_STARTED,
|
||||||
|
data: plainLanguage,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const file = fs.createWriteStream(destinationPath);
|
||||||
|
const req = http.request(
|
||||||
|
`http://i18n.lbry.io/langs/${lang}`,
|
||||||
|
response => {
|
||||||
|
file.on("finish", () => {
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
// push to our local list
|
||||||
|
dispatch({
|
||||||
|
type: types.DOWNLOAD_LANGUAGE_SUCCEEDED,
|
||||||
|
data: plainLanguage,
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
response.pipe(file);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const errorHandler = err => {
|
||||||
|
if (file) {
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.unlink(destinationPath); // Delete the file async. (But we don't check the result)
|
||||||
|
dispatch({
|
||||||
|
type: types.DOWNLOAD_LANGUAGE_FAILED,
|
||||||
|
data: plainLanguage,
|
||||||
|
});
|
||||||
|
reject(err);
|
||||||
|
};
|
||||||
|
req.on("error", errorHandler);
|
||||||
|
file.on("error", errorHandler);
|
||||||
|
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doDownloadLanguages() {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
if (!fs.existsSync("app/locales")) {
|
||||||
|
fs.mkdirSync("app/locales");
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: types.DOWNLOAD_LANGUAGES_STARTED,
|
||||||
|
data: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const req = http.request(
|
||||||
|
{ host: "i18n.lbry.io", path: "/" },
|
||||||
|
response => {
|
||||||
|
let str = "";
|
||||||
|
|
||||||
|
response.on("data", chunk => {
|
||||||
|
str += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
response.on("end", () => {
|
||||||
|
const files = JSON.parse(str);
|
||||||
|
const actions = [];
|
||||||
|
files.forEach(file => {
|
||||||
|
actions.push(doDownloadLanguage(file, `app/locales/${file}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(batchActions(...actions));
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: types.DOWNLOAD_LANGUAGES_COMPLETED,
|
||||||
|
data: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
req.on("error", err => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import * as types from "constants/action_types";
|
import * as types from "constants/action_types";
|
||||||
|
import * as modals from "constants/modal_types";
|
||||||
import lbryio from "lbryio";
|
import lbryio from "lbryio";
|
||||||
import { setLocal } from "utils";
|
import { setLocal } from "utils";
|
||||||
import { doRewardList } from "actions/rewards";
|
import { doOpenModal } from "actions/app";
|
||||||
import { selectEmailToVerify } from "selectors/user";
|
import { doRewardList, doClaimRewardType } from "actions/rewards";
|
||||||
|
import { selectEmailToVerify, selectUser } from "selectors/user";
|
||||||
|
import rewards from "rewards";
|
||||||
|
|
||||||
export function doAuthenticate() {
|
export function doAuthenticate() {
|
||||||
return function(dispatch, getState) {
|
return function(dispatch, getState) {
|
||||||
|
@ -19,6 +22,7 @@ export function doAuthenticate() {
|
||||||
dispatch(doRewardList());
|
dispatch(doRewardList());
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
dispatch(doOpenModal(modals.AUTHENTICATION_FAILURE));
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.AUTHENTICATION_FAILURE,
|
type: types.AUTHENTICATION_FAILURE,
|
||||||
data: { error },
|
data: { error },
|
||||||
|
@ -136,3 +140,45 @@ export function doUserEmailVerify(verificationToken) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doUserIdentityVerify(stripeToken) {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
dispatch({
|
||||||
|
type: types.USER_IDENTITY_VERIFY_STARTED,
|
||||||
|
token: stripeToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
lbryio
|
||||||
|
.call("user", "verify_identity", { stripe_token: stripeToken }, "post")
|
||||||
|
.then(user => {
|
||||||
|
if (user.is_identity_verified) {
|
||||||
|
dispatch({
|
||||||
|
type: types.USER_IDENTITY_VERIFY_SUCCESS,
|
||||||
|
data: { user },
|
||||||
|
});
|
||||||
|
dispatch(doClaimRewardType(rewards.TYPE_NEW_USER));
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
"Your identity is still not verified. This should not happen."
|
||||||
|
); //shouldn't happen
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dispatch({
|
||||||
|
type: types.USER_IDENTITY_VERIFY_FAILURE,
|
||||||
|
data: { error: error.toString() },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doFetchAccessToken() {
|
||||||
|
return function(dispatch, getState) {
|
||||||
|
const success = token =>
|
||||||
|
dispatch({
|
||||||
|
type: types.FETCH_ACCESS_TOKEN_SUCCESS,
|
||||||
|
data: { token },
|
||||||
|
});
|
||||||
|
lbryio.getAuthToken().then(success);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@ import store from "store.js";
|
||||||
import lbry from "./lbry.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")
|
const language = lbry.getClientSetting("language")
|
||||||
? lbry.getClientSetting("language")
|
? lbry.getClientSetting("language")
|
||||||
: "en";
|
: "en";
|
||||||
|
|
|
@ -1,19 +1,33 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { selectCurrentModal } from "selectors/app";
|
import { selectCurrentModal } from "selectors/app";
|
||||||
import { doCheckUpgradeAvailable, doAlertError } from "actions/app";
|
import {
|
||||||
import { doUpdateBalance } from "actions/wallet";
|
doCheckUpgradeAvailable,
|
||||||
import App from "./view";
|
doOpenModal,
|
||||||
|
doAlertError,
|
||||||
|
doRecordScroll,
|
||||||
|
} from "actions/app";
|
||||||
|
import { doFetchRewardedContent } from "actions/content";
|
||||||
|
|
||||||
const select = state => ({
|
import { doUpdateBalance } from "actions/wallet";
|
||||||
|
import { selectWelcomeModalAcknowledged } from "selectors/app";
|
||||||
|
import { selectUser } from "selectors/user";
|
||||||
|
import App from "./view";
|
||||||
|
import * as modals from "constants/modal_types";
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
modal: selectCurrentModal(state),
|
modal: selectCurrentModal(state),
|
||||||
|
isWelcomeAcknowledged: selectWelcomeModalAcknowledged(state),
|
||||||
|
user: selectUser(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
alertError: errorList => dispatch(doAlertError(errorList)),
|
alertError: errorList => dispatch(doAlertError(errorList)),
|
||||||
checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()),
|
checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()),
|
||||||
|
openWelcomeModal: () => dispatch(doOpenModal(modals.WELCOME)),
|
||||||
updateBalance: balance => dispatch(doUpdateBalance(balance)),
|
updateBalance: balance => dispatch(doUpdateBalance(balance)),
|
||||||
|
fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
|
||||||
|
recordScroll: scrollPosition => dispatch(doRecordScroll(scrollPosition)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(App);
|
export default connect(select, perform)(App);
|
||||||
|
|
|
@ -2,25 +2,64 @@ 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 ModalError from "component/modalError";
|
import ModalError from "component/modalError";
|
||||||
|
import ModalAuthFailure from "component/modalAuthFailure";
|
||||||
import ModalDownloading from "component/modalDownloading";
|
import ModalDownloading from "component/modalDownloading";
|
||||||
import UpgradeModal from "component/modalUpgrade";
|
import ModalInsufficientCredits from "component/modalInsufficientCredits";
|
||||||
import WelcomeModal from "component/modalWelcome";
|
import ModalUpgrade from "component/modalUpgrade";
|
||||||
|
import ModalWelcome from "component/modalWelcome";
|
||||||
|
import ModalFirstReward from "component/modalFirstReward";
|
||||||
import lbry from "lbry";
|
import lbry from "lbry";
|
||||||
import { Line } from "rc-progress";
|
import * as modals from "constants/modal_types";
|
||||||
|
|
||||||
class App extends React.PureComponent {
|
class App extends React.PureComponent {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
|
const {
|
||||||
|
alertError,
|
||||||
|
checkUpgradeAvailable,
|
||||||
|
updateBalance,
|
||||||
|
fetchRewardedContent,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
document.addEventListener("unhandledError", event => {
|
document.addEventListener("unhandledError", event => {
|
||||||
this.props.alertError(event.detail);
|
alertError(event.detail);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.props.upgradeSkipped) {
|
if (!this.props.upgradeSkipped) {
|
||||||
this.props.checkUpgradeAvailable();
|
checkUpgradeAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
lbry.balanceSubscribe(balance => {
|
lbry.balanceSubscribe(balance => {
|
||||||
this.props.updateBalance(balance);
|
updateBalance(balance);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fetchRewardedContent();
|
||||||
|
|
||||||
|
this.showWelcome(this.props);
|
||||||
|
|
||||||
|
this.scrollListener = () => this.props.recordScroll(window.scrollY);
|
||||||
|
|
||||||
|
window.addEventListener("scroll", this.scrollListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
this.showWelcome(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
showWelcome(props) {
|
||||||
|
const { isWelcomeAcknowledged, openWelcomeModal, user } = props;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isWelcomeAcknowledged &&
|
||||||
|
user &&
|
||||||
|
!user.is_reward_approved &&
|
||||||
|
!user.is_identity_verified
|
||||||
|
) {
|
||||||
|
openWelcomeModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
window.removeEventListener("scroll", this.scrollListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -32,10 +71,13 @@ class App extends React.PureComponent {
|
||||||
<div id="main-content">
|
<div id="main-content">
|
||||||
<Router />
|
<Router />
|
||||||
</div>
|
</div>
|
||||||
{modal == "upgrade" && <UpgradeModal />}
|
{modal == modals.UPGRADE && <ModalUpgrade />}
|
||||||
{modal == "downloading" && <ModalDownloading />}
|
{modal == modals.DOWNLOADING && <ModalDownloading />}
|
||||||
{modal == "error" && <ModalError />}
|
{modal == modals.ERROR && <ModalError />}
|
||||||
{modal == "welcome" && <WelcomeModal />}
|
{modal == modals.INSUFFICIENT_CREDITS && <ModalInsufficientCredits />}
|
||||||
|
{modal == modals.WELCOME && <ModalWelcome />}
|
||||||
|
{modal == modals.FIRST_REWARD && <ModalFirstReward />}
|
||||||
|
{modal == modals.AUTHENTICATION_FAILURE && <ModalAuthFailure />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
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);
|
|
|
@ -1,22 +0,0 @@
|
||||||
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;
|
|
|
@ -1,33 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import * as modal from "constants/modal_types";
|
|
||||||
import rewards from "rewards.js";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { doUserEmailDecline } from "actions/user";
|
|
||||||
import { doOpenModal } from "actions/app";
|
|
||||||
import {
|
|
||||||
selectAuthenticationIsPending,
|
|
||||||
selectUserHasEmail,
|
|
||||||
selectUserIsAuthRequested,
|
|
||||||
} from "selectors/user";
|
|
||||||
import { makeSelectHasClaimedReward } from "selectors/rewards";
|
|
||||||
import AuthOverlay from "./view";
|
|
||||||
|
|
||||||
const select = (state, props) => {
|
|
||||||
const selectHasClaimed = makeSelectHasClaimedReward();
|
|
||||||
|
|
||||||
return {
|
|
||||||
hasEmail: selectUserHasEmail(state),
|
|
||||||
isPending: selectAuthenticationIsPending(state),
|
|
||||||
isShowing: selectUserIsAuthRequested(state),
|
|
||||||
hasNewUserReward: selectHasClaimed(state, {
|
|
||||||
reward_type: rewards.TYPE_NEW_USER,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const perform = dispatch => ({
|
|
||||||
userEmailDecline: () => dispatch(doUserEmailDecline()),
|
|
||||||
openWelcomeModal: () => dispatch(doOpenModal(modal.WELCOME)),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(select, perform)(AuthOverlay);
|
|
|
@ -1,90 +0,0 @@
|
||||||
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";
|
|
||||||
import { getLocal, setLocal } from "utils";
|
|
||||||
|
|
||||||
export class AuthOverlay extends React.PureComponent {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
showNoEmailConfirm: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
if (
|
|
||||||
this.props.isShowing &&
|
|
||||||
!this.props.isPending &&
|
|
||||||
!nextProps.hasNewUserReward &&
|
|
||||||
!nextProps.isShowing /* && !getLocal("welcome_screen_shown")*/
|
|
||||||
) {
|
|
||||||
setLocal("welcome_screen_shown", true);
|
|
||||||
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;
|
|
8
ui/js/component/cardMedia/index.js
Normal file
8
ui/js/component/cardMedia/index.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import CardMedia from "./view";
|
||||||
|
|
||||||
|
const select = state => ({});
|
||||||
|
const perform = dispatch => ({});
|
||||||
|
|
||||||
|
export default connect(select, perform)(CardMedia);
|
54
ui/js/component/cardMedia/view.jsx
Normal file
54
ui/js/component/cardMedia/view.jsx
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
class CardMedia extends React.PureComponent {
|
||||||
|
static AUTO_THUMB_CLASSES = [
|
||||||
|
"purple",
|
||||||
|
"red",
|
||||||
|
"pink",
|
||||||
|
"indigo",
|
||||||
|
"blue",
|
||||||
|
"light-blue",
|
||||||
|
"cyan",
|
||||||
|
"teal",
|
||||||
|
"green",
|
||||||
|
"yellow",
|
||||||
|
"orange",
|
||||||
|
];
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.setState({
|
||||||
|
autoThumbClass:
|
||||||
|
CardMedia.AUTO_THUMB_CLASSES[
|
||||||
|
Math.floor(Math.random() * CardMedia.AUTO_THUMB_CLASSES.length)
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { title, thumbnail } = this.props;
|
||||||
|
const atClass = this.state.autoThumbClass;
|
||||||
|
|
||||||
|
if (thumbnail) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="card__media"
|
||||||
|
style={{ backgroundImage: "url('" + thumbnail + "')" }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`card__media card__media--autothumb ${atClass}`}>
|
||||||
|
<div className="card__autothumb__text">
|
||||||
|
{title &&
|
||||||
|
title
|
||||||
|
.replace(/\s+/g, "")
|
||||||
|
.substring(0, Math.min(title.replace(" ", "").length, 5))
|
||||||
|
.toUpperCase()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CardMedia;
|
12
ui/js/component/cardVerify/index.js
Normal file
12
ui/js/component/cardVerify/index.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { selectUserEmail } from "selectors/user";
|
||||||
|
import CardVerify from "./view";
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
email: selectUserEmail(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({});
|
||||||
|
|
||||||
|
export default connect(select, perform)(CardVerify);
|
180
ui/js/component/cardVerify/view.jsx
Normal file
180
ui/js/component/cardVerify/view.jsx
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
import React from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import Link from "component/link";
|
||||||
|
|
||||||
|
let scriptLoading = false;
|
||||||
|
let scriptLoaded = false;
|
||||||
|
let scriptDidError = false;
|
||||||
|
|
||||||
|
class CardVerify extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
|
||||||
|
label: PropTypes.string,
|
||||||
|
|
||||||
|
// =====================================================
|
||||||
|
// Required by stripe
|
||||||
|
// see Stripe docs for more info:
|
||||||
|
// https://stripe.com/docs/checkout#integration-custom
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
// Your publishable key (test or live).
|
||||||
|
// can't use "key" as a prop in react, so have to change the keyname
|
||||||
|
stripeKey: PropTypes.string.isRequired,
|
||||||
|
|
||||||
|
// The callback to invoke when the Checkout process is complete.
|
||||||
|
// function(token)
|
||||||
|
// token is the token object created.
|
||||||
|
// token.id can be used to create a charge or customer.
|
||||||
|
// token.email contains the email address entered by the user.
|
||||||
|
token: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
open: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (scriptLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scriptLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptLoading = true;
|
||||||
|
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src = "https://checkout.stripe.com/checkout.js";
|
||||||
|
script.async = 1;
|
||||||
|
|
||||||
|
this.loadPromise = (() => {
|
||||||
|
let canceled = false;
|
||||||
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
script.onload = () => {
|
||||||
|
scriptLoaded = true;
|
||||||
|
scriptLoading = false;
|
||||||
|
resolve();
|
||||||
|
this.onScriptLoaded();
|
||||||
|
};
|
||||||
|
script.onerror = event => {
|
||||||
|
scriptDidError = true;
|
||||||
|
scriptLoading = false;
|
||||||
|
reject(event);
|
||||||
|
this.onScriptError(event);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const wrappedPromise = new Promise((accept, cancel) => {
|
||||||
|
promise.then(
|
||||||
|
() => (canceled ? cancel({ isCanceled: true }) : accept())
|
||||||
|
);
|
||||||
|
promise.catch(
|
||||||
|
error => (canceled ? cancel({ isCanceled: true }) : cancel(error))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
promise: wrappedPromise,
|
||||||
|
cancel() {
|
||||||
|
canceled = true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
this.loadPromise.promise
|
||||||
|
.then(this.onScriptLoaded)
|
||||||
|
.catch(this.onScriptError);
|
||||||
|
|
||||||
|
document.body.appendChild(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
if (!scriptLoading) {
|
||||||
|
this.updateStripeHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.loadPromise) {
|
||||||
|
this.loadPromise.cancel();
|
||||||
|
}
|
||||||
|
if (CardVerify.stripeHandler && this.state.open) {
|
||||||
|
CardVerify.stripeHandler.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onScriptLoaded = () => {
|
||||||
|
if (!CardVerify.stripeHandler) {
|
||||||
|
CardVerify.stripeHandler = StripeCheckout.configure({
|
||||||
|
key: this.props.stripeKey,
|
||||||
|
});
|
||||||
|
if (this.hasPendingClick) {
|
||||||
|
this.showStripeDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onScriptError = (...args) => {
|
||||||
|
throw new Error("Unable to load credit validation script.");
|
||||||
|
};
|
||||||
|
|
||||||
|
onClosed = () => {
|
||||||
|
this.setState({ open: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
updateStripeHandler() {
|
||||||
|
if (!CardVerify.stripeHandler) {
|
||||||
|
CardVerify.stripeHandler = StripeCheckout.configure({
|
||||||
|
key: this.props.stripeKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showStripeDialog() {
|
||||||
|
this.setState({ open: true });
|
||||||
|
CardVerify.stripeHandler.open({
|
||||||
|
allowRememberMe: false,
|
||||||
|
closed: this.onClosed,
|
||||||
|
description: __("Confirm Identity"),
|
||||||
|
email: this.props.email,
|
||||||
|
locale: "auto",
|
||||||
|
panelLabel: "Verify",
|
||||||
|
token: this.props.token,
|
||||||
|
zipCode: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick = () => {
|
||||||
|
if (scriptDidError) {
|
||||||
|
try {
|
||||||
|
throw new Error(
|
||||||
|
"Tried to call onClick, but StripeCheckout failed to load"
|
||||||
|
);
|
||||||
|
} catch (x) {}
|
||||||
|
} else if (CardVerify.stripeHandler) {
|
||||||
|
this.showStripeDialog();
|
||||||
|
} else {
|
||||||
|
this.hasPendingClick = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
button="primary"
|
||||||
|
label={this.props.label}
|
||||||
|
icon="icon-lock"
|
||||||
|
disabled={
|
||||||
|
this.props.disabled || this.state.open || this.hasPendingClick
|
||||||
|
}
|
||||||
|
onClick={this.onClick.bind(this)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CardVerify;
|
|
@ -1,4 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { formatCredits } from "utils";
|
||||||
import lbry from "../lbry.js";
|
import lbry from "../lbry.js";
|
||||||
|
|
||||||
//component/icon.js
|
//component/icon.js
|
||||||
|
@ -78,7 +79,7 @@ export class CreditAmount extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const formattedAmount = lbry.formatCredits(
|
const formattedAmount = formatCredits(
|
||||||
this.props.amount,
|
this.props.amount,
|
||||||
this.props.precision
|
this.props.precision
|
||||||
);
|
);
|
||||||
|
@ -140,7 +141,7 @@ export class Address extends React.PureComponent {
|
||||||
}}
|
}}
|
||||||
style={addressStyle}
|
style={addressStyle}
|
||||||
readOnly="readonly"
|
readOnly="readonly"
|
||||||
value={this.props.address}
|
value={this.props.address || ""}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { doCloseModal, doOpenModal } from "actions/app";
|
||||||
import { doFetchAvailability } from "actions/availability";
|
import { doFetchAvailability } from "actions/availability";
|
||||||
import { doOpenFileInShell, doOpenFileInFolder } from "actions/file_info";
|
import { doOpenFileInShell, doOpenFileInFolder } from "actions/file_info";
|
||||||
import { makeSelectClaimForUriIsMine } from "selectors/claims";
|
import { makeSelectClaimForUriIsMine } from "selectors/claims";
|
||||||
import { doPurchaseUri, doLoadVideo } from "actions/content";
|
import { doPurchaseUri, doLoadVideo, doStartDownload } from "actions/content";
|
||||||
import FileActions from "./view";
|
import FileActions from "./view";
|
||||||
|
|
||||||
const makeSelect = () => {
|
const makeSelect = () => {
|
||||||
|
@ -47,6 +47,7 @@ const perform = dispatch => ({
|
||||||
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)),
|
||||||
|
restartDownload: (uri, outpoint) => dispatch(doStartDownload(uri, outpoint)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(makeSelect, perform)(FileActions);
|
export default connect(makeSelect, perform)(FileActions);
|
||||||
|
|
|
@ -22,6 +22,21 @@ class FileActions extends React.PureComponent {
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
this.checkAvailability(nextProps.uri);
|
this.checkAvailability(nextProps.uri);
|
||||||
|
this.restartDownload(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
restartDownload(props) {
|
||||||
|
const { downloading, fileInfo, uri, restartDownload } = props;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!downloading &&
|
||||||
|
fileInfo &&
|
||||||
|
!fileInfo.completed &&
|
||||||
|
fileInfo.written_bytes !== false &&
|
||||||
|
fileInfo.written_bytes < fileInfo.total_bytes
|
||||||
|
) {
|
||||||
|
restartDownload(uri, fileInfo.outpoint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAvailability(uri) {
|
checkAvailability(uri) {
|
||||||
|
@ -142,6 +157,8 @@ class FileActions extends React.PureComponent {
|
||||||
onClick={() => openInShell(fileInfo)}
|
onClick={() => openInShell(fileInfo)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
} else if (!fileInfo) {
|
||||||
|
content = <BusyMessage message={__("Fetching file info")} />;
|
||||||
} else {
|
} else {
|
||||||
console.log("handle this case of file action props?");
|
console.log("handle this case of file action props?");
|
||||||
}
|
}
|
||||||
|
@ -176,13 +193,6 @@ class FileActions extends React.PureComponent {
|
||||||
</strong>{" "}
|
</strong>{" "}
|
||||||
{__("credits")}.
|
{__("credits")}.
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal
|
|
||||||
isOpen={modal == "notEnoughCredits"}
|
|
||||||
contentLabel={__("Not enough credits")}
|
|
||||||
onConfirmed={closeModal}
|
|
||||||
>
|
|
||||||
{__("You don't have enough LBRY credits to pay for this stream.")}
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={modal == "timedOut"}
|
isOpen={modal == "timedOut"}
|
||||||
contentLabel={__("Download failed")}
|
contentLabel={__("Download failed")}
|
||||||
|
|
|
@ -8,7 +8,10 @@ import {
|
||||||
makeSelectMetadataForUri,
|
makeSelectMetadataForUri,
|
||||||
} from "selectors/claims";
|
} from "selectors/claims";
|
||||||
import { makeSelectFileInfoForUri } from "selectors/file_info";
|
import { makeSelectFileInfoForUri } from "selectors/file_info";
|
||||||
import { makeSelectIsResolvingForUri } from "selectors/content";
|
import {
|
||||||
|
makeSelectIsResolvingForUri,
|
||||||
|
selectRewardContentClaimIds,
|
||||||
|
} from "selectors/content";
|
||||||
import FileCard from "./view";
|
import FileCard from "./view";
|
||||||
|
|
||||||
const makeSelect = () => {
|
const makeSelect = () => {
|
||||||
|
@ -22,6 +25,7 @@ const makeSelect = () => {
|
||||||
fileInfo: selectFileInfoForUri(state, props),
|
fileInfo: selectFileInfoForUri(state, props),
|
||||||
obscureNsfw: !selectShowNsfw(state),
|
obscureNsfw: !selectShowNsfw(state),
|
||||||
metadata: selectMetadataForUri(state, props),
|
metadata: selectMetadataForUri(state, props),
|
||||||
|
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
|
||||||
isResolvingUri: selectResolvingUri(state, props),
|
isResolvingUri: selectResolvingUri(state, props),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import lbryuri from "lbryuri.js";
|
import lbryuri from "lbryuri.js";
|
||||||
|
import CardMedia from "component/cardMedia";
|
||||||
import Link from "component/link";
|
import Link from "component/link";
|
||||||
import { TruncatedText, Icon } from "component/common";
|
import { TruncatedText, Icon } from "component/common";
|
||||||
|
import IconFeatured from "component/iconFeatured";
|
||||||
import FilePrice from "component/filePrice";
|
import FilePrice from "component/filePrice";
|
||||||
import UriIndicator from "component/uriIndicator";
|
import UriIndicator from "component/uriIndicator";
|
||||||
import NsfwOverlay from "component/nsfwOverlay";
|
import NsfwOverlay from "component/nsfwOverlay";
|
||||||
|
import TruncatedMarkdown from "component/truncatedMarkdown";
|
||||||
|
|
||||||
class FileCard extends React.PureComponent {
|
class FileCard extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -44,11 +47,23 @@ class FileCard extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { claim, fileInfo, metadata, isResolvingUri, navigate } = this.props;
|
const {
|
||||||
|
claim,
|
||||||
|
fileInfo,
|
||||||
|
metadata,
|
||||||
|
isResolvingUri,
|
||||||
|
navigate,
|
||||||
|
rewardedContentClaimIds,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
const uri = lbryuri.normalize(this.props.uri);
|
const uri = lbryuri.normalize(this.props.uri);
|
||||||
const title = metadata && metadata.title ? metadata.title : uri;
|
const title = metadata && metadata.title ? metadata.title : uri;
|
||||||
|
const thumbnail = metadata && metadata.thumbnail
|
||||||
|
? metadata.thumbnail
|
||||||
|
: null;
|
||||||
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
||||||
|
const isRewardContent =
|
||||||
|
claim && rewardedContentClaimIds.includes(claim.claim_id);
|
||||||
|
|
||||||
let description = "";
|
let description = "";
|
||||||
if (isResolvingUri && !claim) {
|
if (isResolvingUri && !claim) {
|
||||||
|
@ -73,28 +88,23 @@ class FileCard extends React.PureComponent {
|
||||||
onClick={() => navigate("/show", { uri })}
|
onClick={() => navigate("/show", { uri })}
|
||||||
className="card__link"
|
className="card__link"
|
||||||
>
|
>
|
||||||
|
<CardMedia title={title} thumbnail={thumbnail} />
|
||||||
<div className="card__title-identity">
|
<div className="card__title-identity">
|
||||||
<h5 title={title}>
|
<div className="card__title" title={title}>
|
||||||
<TruncatedText lines={1}>{title}</TruncatedText>
|
<TruncatedText lines={1}>{title}</TruncatedText>
|
||||||
</h5>
|
</div>
|
||||||
<div className="card__subtitle">
|
<div className="card__subtitle">
|
||||||
<span style={{ float: "right" }}>
|
<span style={{ float: "right" }}>
|
||||||
<FilePrice uri={uri} />
|
<FilePrice uri={uri} />
|
||||||
{fileInfo
|
{isRewardContent && <span>{" "}<IconFeatured /></span>}
|
||||||
? <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 &&
|
|
||||||
<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>
|
<TruncatedMarkdown lines={2}>{description}</TruncatedMarkdown>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@ 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/formField";
|
||||||
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";
|
||||||
|
@ -67,7 +67,9 @@ class FileList extends React.PureComponent {
|
||||||
const content = [];
|
const content = [];
|
||||||
|
|
||||||
this._sortFunctions[sortBy](fileInfos).forEach(fileInfo => {
|
this._sortFunctions[sortBy](fileInfos).forEach(fileInfo => {
|
||||||
let uriParams = {};
|
let uriParams = {
|
||||||
|
claimId: fileInfo.claim_id,
|
||||||
|
};
|
||||||
if (fileInfo.channel_name) {
|
if (fileInfo.channel_name) {
|
||||||
uriParams.channelName = fileInfo.channel_name;
|
uriParams.channelName = fileInfo.channel_name;
|
||||||
uriParams.contentName = fileInfo.name;
|
uriParams.contentName = fileInfo.name;
|
||||||
|
@ -79,7 +81,7 @@ class FileList extends React.PureComponent {
|
||||||
|
|
||||||
content.push(
|
content.push(
|
||||||
<FileTile
|
<FileTile
|
||||||
key={uri}
|
key={fileInfo.outpoint || fileInfo.claim_id}
|
||||||
uri={uri}
|
uri={uri}
|
||||||
hidePrice={true}
|
hidePrice={true}
|
||||||
showEmpty={this.props.fileTileShowEmpty}
|
showEmpty={this.props.fileTileShowEmpty}
|
||||||
|
@ -94,7 +96,6 @@ class FileList extends React.PureComponent {
|
||||||
<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>
|
|
||||||
</FormField>
|
</FormField>
|
||||||
</span>
|
</span>
|
||||||
{content}
|
{content}
|
||||||
|
|
|
@ -67,7 +67,7 @@ class FileListSearch extends React.PureComponent {
|
||||||
|
|
||||||
{results && !!results.length
|
{results && !!results.length
|
||||||
? <FileListSearchResults {...this.props} />
|
? <FileListSearchResults {...this.props} />
|
||||||
: <SearchNoResults {...this.props} />}
|
: !isSearching && <SearchNoResults {...this.props} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,10 @@ import {
|
||||||
} from "selectors/claims";
|
} from "selectors/claims";
|
||||||
import { makeSelectFileInfoForUri } from "selectors/file_info";
|
import { makeSelectFileInfoForUri } from "selectors/file_info";
|
||||||
import { selectShowNsfw } from "selectors/settings";
|
import { selectShowNsfw } from "selectors/settings";
|
||||||
import { makeSelectIsResolvingForUri } from "selectors/content";
|
import {
|
||||||
|
makeSelectIsResolvingForUri,
|
||||||
|
selectRewardContentClaimIds,
|
||||||
|
} from "selectors/content";
|
||||||
import FileTile from "./view";
|
import FileTile from "./view";
|
||||||
|
|
||||||
const makeSelect = () => {
|
const makeSelect = () => {
|
||||||
|
@ -23,6 +26,7 @@ const makeSelect = () => {
|
||||||
obscureNsfw: !selectShowNsfw(state),
|
obscureNsfw: !selectShowNsfw(state),
|
||||||
metadata: selectMetadataForUri(state, props),
|
metadata: selectMetadataForUri(state, props),
|
||||||
isResolvingUri: selectResolvingUri(state, props),
|
isResolvingUri: selectResolvingUri(state, props),
|
||||||
|
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
|
||||||
});
|
});
|
||||||
|
|
||||||
return select;
|
return select;
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import lbry from "lbry.js";
|
|
||||||
import lbryuri from "lbryuri.js";
|
import lbryuri from "lbryuri.js";
|
||||||
|
import CardMedia from "component/cardMedia";
|
||||||
import Link from "component/link";
|
import Link from "component/link";
|
||||||
import { TruncatedText } from "component/common.js";
|
import { TruncatedText } from "component/common.js";
|
||||||
import FilePrice from "component/filePrice";
|
import FilePrice from "component/filePrice";
|
||||||
import NsfwOverlay from "component/nsfwOverlay";
|
import NsfwOverlay from "component/nsfwOverlay";
|
||||||
|
import IconFeatured from "component/iconFeatured";
|
||||||
|
|
||||||
class FileTile extends React.PureComponent {
|
class FileTile extends React.PureComponent {
|
||||||
static SHOW_EMPTY_PUBLISH = "publish";
|
static SHOW_EMPTY_PUBLISH = "publish";
|
||||||
|
@ -57,6 +58,7 @@ class FileTile extends React.PureComponent {
|
||||||
showEmpty,
|
showEmpty,
|
||||||
navigate,
|
navigate,
|
||||||
hidePrice,
|
hidePrice,
|
||||||
|
rewardedContentClaimIds,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const uri = lbryuri.normalize(this.props.uri);
|
const uri = lbryuri.normalize(this.props.uri);
|
||||||
|
@ -64,8 +66,14 @@ class FileTile extends React.PureComponent {
|
||||||
const isClaimable = lbryuri.isClaimable(uri);
|
const isClaimable = lbryuri.isClaimable(uri);
|
||||||
const title = isClaimed && metadata && metadata.title
|
const title = isClaimed && metadata && metadata.title
|
||||||
? metadata.title
|
? metadata.title
|
||||||
: uri;
|
: lbryuri.parse(uri).contentName;
|
||||||
|
const thumbnail = metadata && metadata.thumbnail
|
||||||
|
? metadata.thumbnail
|
||||||
|
: null;
|
||||||
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
|
||||||
|
const isRewardContent =
|
||||||
|
claim && rewardedContentClaimIds.includes(claim.claim_id);
|
||||||
|
|
||||||
let onClick = () => navigate("/show", { uri });
|
let onClick = () => navigate("/show", { uri });
|
||||||
|
|
||||||
let description = "";
|
let description = "";
|
||||||
|
@ -98,22 +106,15 @@ class FileTile extends React.PureComponent {
|
||||||
>
|
>
|
||||||
<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
|
<CardMedia title={title} thumbnail={thumbnail} />
|
||||||
className="card__media"
|
|
||||||
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}
|
||||||
|
{isRewardContent && <IconFeatured />}
|
||||||
<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>
|
||||||
<div className="card__content card__subtext">
|
<div className="card__content card__subtext">
|
||||||
<TruncatedText lines={3}>
|
<TruncatedText lines={3}>
|
||||||
|
|
|
@ -1,181 +1,32 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import FileSelector from "./file-selector.js";
|
import FormField from "component/formField";
|
||||||
import { Icon } from "./common.js";
|
|
||||||
|
|
||||||
var formFieldCounter = 0,
|
let formFieldCounter = 0;
|
||||||
formFieldFileSelectorTypes = ["file", "directory"],
|
|
||||||
formFieldNestedLabelTypes = ["radio", "checkbox"];
|
|
||||||
|
|
||||||
function formFieldId() {
|
export const formFieldNestedLabelTypes = ["radio", "checkbox"];
|
||||||
|
|
||||||
|
export function formFieldId() {
|
||||||
return "form-field-" + ++formFieldCounter;
|
return "form-field-" + ++formFieldCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FormField extends React.PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
type: React.PropTypes.string.isRequired,
|
|
||||||
prefix: React.PropTypes.string,
|
|
||||||
postfix: React.PropTypes.string,
|
|
||||||
hasError: React.PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this._fieldRequiredText = __("This field is required");
|
|
||||||
this._type = null;
|
|
||||||
this._element = null;
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isError: null,
|
|
||||||
errorMessage: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
if (["text", "number", "radio", "checkbox"].includes(this.props.type)) {
|
|
||||||
this._element = "input";
|
|
||||||
this._type = this.props.type;
|
|
||||||
} else if (this.props.type == "text-number") {
|
|
||||||
this._element = "input";
|
|
||||||
this._type = "text";
|
|
||||||
} else if (formFieldFileSelectorTypes.includes(this.props.type)) {
|
|
||||||
this._element = "input";
|
|
||||||
this._type = "hidden";
|
|
||||||
} else {
|
|
||||||
// Non <input> field, e.g. <select>, <textarea>
|
|
||||||
this._element = this.props.type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
/**
|
|
||||||
* We have to add the webkitdirectory attribute here because React doesn't allow it in JSX
|
|
||||||
* https://github.com/facebook/react/issues/3468
|
|
||||||
*/
|
|
||||||
if (this.props.type == "directory") {
|
|
||||||
this.refs.field.webkitdirectory = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFileChosen(path) {
|
|
||||||
this.refs.field.value = path;
|
|
||||||
if (this.props.onChange) {
|
|
||||||
// Updating inputs programmatically doesn't generate an event, so we have to make our own
|
|
||||||
const event = new Event("change", { bubbles: true });
|
|
||||||
this.refs.field.dispatchEvent(event); // This alone won't generate a React event, but we use it to attach the field as a target
|
|
||||||
this.props.onChange(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showError(text) {
|
|
||||||
this.setState({
|
|
||||||
isError: true,
|
|
||||||
errorMessage: text,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
focus() {
|
|
||||||
this.refs.field.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
getValue() {
|
|
||||||
if (this.props.type == "checkbox") {
|
|
||||||
return this.refs.field.checked;
|
|
||||||
} else {
|
|
||||||
return this.refs.field.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getSelectedElement() {
|
|
||||||
return this.refs.field.options[this.refs.field.selectedIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
// Pass all unhandled props to the field element
|
|
||||||
const otherProps = Object.assign({}, this.props),
|
|
||||||
isError = this.state.isError !== null
|
|
||||||
? this.state.isError
|
|
||||||
: this.props.hasError,
|
|
||||||
elementId = this.props.id ? this.props.id : formFieldId(),
|
|
||||||
renderElementInsideLabel =
|
|
||||||
this.props.label && formFieldNestedLabelTypes.includes(this.props.type);
|
|
||||||
|
|
||||||
delete otherProps.type;
|
|
||||||
delete otherProps.label;
|
|
||||||
delete otherProps.hasError;
|
|
||||||
delete otherProps.className;
|
|
||||||
delete otherProps.postfix;
|
|
||||||
delete otherProps.prefix;
|
|
||||||
|
|
||||||
const element = (
|
|
||||||
<this._element
|
|
||||||
id={elementId}
|
|
||||||
type={this._type}
|
|
||||||
name={this.props.name}
|
|
||||||
ref="field"
|
|
||||||
placeholder={this.props.placeholder}
|
|
||||||
className={
|
|
||||||
"form-field__input form-field__input-" +
|
|
||||||
this.props.type +
|
|
||||||
" " +
|
|
||||||
(this.props.className || "") +
|
|
||||||
(isError ? "form-field__input--error" : "")
|
|
||||||
}
|
|
||||||
{...otherProps}
|
|
||||||
>
|
|
||||||
{this.props.children}
|
|
||||||
</this._element>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={"form-field form-field--" + this.props.type}>
|
|
||||||
{this.props.prefix
|
|
||||||
? <span className="form-field__prefix">{this.props.prefix}</span>
|
|
||||||
: ""}
|
|
||||||
{renderElementInsideLabel
|
|
||||||
? <label
|
|
||||||
htmlFor={elementId}
|
|
||||||
className={
|
|
||||||
"form-field__label " +
|
|
||||||
(isError ? "form-field__label--error" : "")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{element}
|
|
||||||
{this.props.label}
|
|
||||||
</label>
|
|
||||||
: element}
|
|
||||||
{formFieldFileSelectorTypes.includes(this.props.type)
|
|
||||||
? <FileSelector
|
|
||||||
type={this.props.type}
|
|
||||||
onFileChosen={this.handleFileChosen.bind(this)}
|
|
||||||
{...(this.props.defaultValue
|
|
||||||
? { initPath: this.props.defaultValue }
|
|
||||||
: {})}
|
|
||||||
/>
|
|
||||||
: null}
|
|
||||||
{this.props.postfix
|
|
||||||
? <span className="form-field__postfix">{this.props.postfix}</span>
|
|
||||||
: ""}
|
|
||||||
{isError && this.state.errorMessage
|
|
||||||
? <div className="form-field__error">{this.state.errorMessage}</div>
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FormRow extends React.PureComponent {
|
export class FormRow extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
label: React.PropTypes.oneOfType([
|
label: React.PropTypes.oneOfType([
|
||||||
React.PropTypes.string,
|
React.PropTypes.string,
|
||||||
React.PropTypes.element,
|
React.PropTypes.element,
|
||||||
]),
|
]),
|
||||||
|
errorMessage: React.PropTypes.oneOfType([
|
||||||
|
React.PropTypes.string,
|
||||||
|
React.PropTypes.object,
|
||||||
|
]),
|
||||||
// helper: React.PropTypes.html,
|
// helper: React.PropTypes.html,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this._field = null;
|
||||||
|
|
||||||
this._fieldRequiredText = __("This field is required");
|
this._fieldRequiredText = __("This field is required");
|
||||||
|
|
||||||
this.state = this.getStateFromProps(props);
|
this.state = this.getStateFromProps(props);
|
||||||
|
@ -190,7 +41,9 @@ export class FormRow extends React.PureComponent {
|
||||||
isError: !!props.errorMessage,
|
isError: !!props.errorMessage,
|
||||||
errorMessage: typeof props.errorMessage === "string"
|
errorMessage: typeof props.errorMessage === "string"
|
||||||
? props.errorMessage
|
? props.errorMessage
|
||||||
: "",
|
: props.errorMessage instanceof Error
|
||||||
|
? props.errorMessage.toString()
|
||||||
|
: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,15 +66,24 @@ export class FormRow extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
getValue() {
|
getValue() {
|
||||||
return this.refs.field.getValue();
|
return this._field.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedElement() {
|
getSelectedElement() {
|
||||||
return this.refs.field.getSelectedElement();
|
return this._field.getSelectedElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
getOptions() {
|
||||||
|
if (!this._field || !this._field.getOptions) {
|
||||||
|
console.log(this);
|
||||||
|
console.log(this._field);
|
||||||
|
console.log(this._field.getOptions);
|
||||||
|
}
|
||||||
|
return this._field.getOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
this.refs.field.focus();
|
this._field.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -257,7 +119,13 @@ export class FormRow extends React.PureComponent {
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
: ""}
|
: ""}
|
||||||
<FormField ref="field" hasError={this.state.isError} {...fieldProps} />
|
<FormField
|
||||||
|
ref={ref => {
|
||||||
|
this._field = ref ? ref.getWrappedInstance() : null;
|
||||||
|
}}
|
||||||
|
hasError={this.state.isError}
|
||||||
|
{...fieldProps}
|
||||||
|
/>
|
||||||
{!this.state.isError && this.props.helper
|
{!this.state.isError && this.props.helper
|
||||||
? <div className="form-field__helper">{this.props.helper}</div>
|
? <div className="form-field__helper">{this.props.helper}</div>
|
||||||
: ""}
|
: ""}
|
||||||
|
|
5
ui/js/component/formField/index.js
Normal file
5
ui/js/component/formField/index.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import FormField from "./view";
|
||||||
|
|
||||||
|
export default connect(null, null, null, { withRef: true })(FormField);
|
178
ui/js/component/formField/view.jsx
Normal file
178
ui/js/component/formField/view.jsx
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
import React from "react";
|
||||||
|
import FileSelector from "component/file-selector.js";
|
||||||
|
import SimpleMDE from "react-simplemde-editor";
|
||||||
|
import { formFieldNestedLabelTypes, formFieldId } from "../form";
|
||||||
|
import style from "react-simplemde-editor/dist/simplemde.min.css";
|
||||||
|
|
||||||
|
const formFieldFileSelectorTypes = ["file", "directory"];
|
||||||
|
|
||||||
|
class FormField extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
type: React.PropTypes.string.isRequired,
|
||||||
|
prefix: React.PropTypes.string,
|
||||||
|
postfix: React.PropTypes.string,
|
||||||
|
hasError: React.PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this._fieldRequiredText = __("This field is required");
|
||||||
|
this._type = null;
|
||||||
|
this._element = null;
|
||||||
|
this._extraElementProps = {};
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isError: null,
|
||||||
|
errorMessage: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
if (["text", "number", "radio", "checkbox"].includes(this.props.type)) {
|
||||||
|
this._element = "input";
|
||||||
|
this._type = this.props.type;
|
||||||
|
} else if (this.props.type == "text-number") {
|
||||||
|
this._element = "input";
|
||||||
|
this._type = "text";
|
||||||
|
} else if (this.props.type == "SimpleMDE") {
|
||||||
|
this._element = SimpleMDE;
|
||||||
|
this._type = "textarea";
|
||||||
|
this._extraElementProps.options = {
|
||||||
|
hideIcons: ["guide", "heading", "image", "fullscreen", "side-by-side"],
|
||||||
|
};
|
||||||
|
} else if (formFieldFileSelectorTypes.includes(this.props.type)) {
|
||||||
|
this._element = "input";
|
||||||
|
this._type = "hidden";
|
||||||
|
} else {
|
||||||
|
// Non <input> field, e.g. <select>, <textarea>
|
||||||
|
this._element = this.props.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
/**
|
||||||
|
* We have to add the webkitdirectory attribute here because React doesn't allow it in JSX
|
||||||
|
* https://github.com/facebook/react/issues/3468
|
||||||
|
*/
|
||||||
|
if (this.props.type == "directory") {
|
||||||
|
this.refs.field.webkitdirectory = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFileChosen(path) {
|
||||||
|
this.refs.field.value = path;
|
||||||
|
if (this.props.onChange) {
|
||||||
|
// Updating inputs programmatically doesn't generate an event, so we have to make our own
|
||||||
|
const event = new Event("change", { bubbles: true });
|
||||||
|
this.refs.field.dispatchEvent(event); // This alone won't generate a React event, but we use it to attach the field as a target
|
||||||
|
this.props.onChange(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showError(text) {
|
||||||
|
this.setState({
|
||||||
|
isError: true,
|
||||||
|
errorMessage: text,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
this.refs.field.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue() {
|
||||||
|
if (this.props.type == "checkbox") {
|
||||||
|
return this.refs.field.checked;
|
||||||
|
} else if (this.props.type == "SimpleMDE") {
|
||||||
|
return this.refs.field.simplemde.value();
|
||||||
|
} else {
|
||||||
|
return this.refs.field.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedElement() {
|
||||||
|
return this.refs.field.options[this.refs.field.selectedIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
getOptions() {
|
||||||
|
return this.refs.field.options;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
// Pass all unhandled props to the field element
|
||||||
|
const otherProps = Object.assign({}, this.props),
|
||||||
|
isError = this.state.isError !== null
|
||||||
|
? this.state.isError
|
||||||
|
: this.props.hasError,
|
||||||
|
elementId = this.props.elementId ? this.props.elementId : formFieldId(),
|
||||||
|
renderElementInsideLabel =
|
||||||
|
this.props.label && formFieldNestedLabelTypes.includes(this.props.type);
|
||||||
|
|
||||||
|
delete otherProps.type;
|
||||||
|
delete otherProps.label;
|
||||||
|
delete otherProps.hasError;
|
||||||
|
delete otherProps.className;
|
||||||
|
delete otherProps.postfix;
|
||||||
|
delete otherProps.prefix;
|
||||||
|
delete otherProps.dispatch;
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<this._element
|
||||||
|
id={elementId}
|
||||||
|
type={this._type}
|
||||||
|
name={this.props.name}
|
||||||
|
ref="field"
|
||||||
|
placeholder={this.props.placeholder}
|
||||||
|
className={
|
||||||
|
"form-field__input form-field__input-" +
|
||||||
|
this.props.type +
|
||||||
|
" " +
|
||||||
|
(this.props.className || "") +
|
||||||
|
(isError ? "form-field__input--error" : "")
|
||||||
|
}
|
||||||
|
{...otherProps}
|
||||||
|
{...this._extraElementProps}
|
||||||
|
>
|
||||||
|
{this.props.children}
|
||||||
|
</this._element>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={"form-field form-field--" + this.props.type}>
|
||||||
|
{this.props.prefix
|
||||||
|
? <span className="form-field__prefix">{this.props.prefix}</span>
|
||||||
|
: ""}
|
||||||
|
{renderElementInsideLabel
|
||||||
|
? <label
|
||||||
|
htmlFor={elementId}
|
||||||
|
className={
|
||||||
|
"form-field__label " +
|
||||||
|
(isError ? "form-field__label--error" : "")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{element}
|
||||||
|
{this.props.label}
|
||||||
|
</label>
|
||||||
|
: element}
|
||||||
|
{formFieldFileSelectorTypes.includes(this.props.type)
|
||||||
|
? <FileSelector
|
||||||
|
type={this.props.type}
|
||||||
|
onFileChosen={this.handleFileChosen.bind(this)}
|
||||||
|
{...(this.props.defaultValue
|
||||||
|
? { initPath: this.props.defaultValue }
|
||||||
|
: {})}
|
||||||
|
/>
|
||||||
|
: null}
|
||||||
|
{this.props.postfix
|
||||||
|
? <span className="form-field__postfix">{this.props.postfix}</span>
|
||||||
|
: ""}
|
||||||
|
{isError && this.state.errorMessage
|
||||||
|
? <div className="form-field__error">{this.state.errorMessage}</div>
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FormField;
|
5
ui/js/component/formFieldPrice/index.js
Normal file
5
ui/js/component/formFieldPrice/index.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import FormFieldPrice from "./view";
|
||||||
|
|
||||||
|
export default connect(null, null)(FormFieldPrice);
|
70
ui/js/component/formFieldPrice/view.jsx
Normal file
70
ui/js/component/formFieldPrice/view.jsx
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import React from "react";
|
||||||
|
import FormField from "component/formField";
|
||||||
|
|
||||||
|
class FormFieldPrice extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
amount: props.defaultValue && props.defaultValue.amount
|
||||||
|
? props.defaultValue.amount
|
||||||
|
: "",
|
||||||
|
currency: props.defaultValue && props.defaultValue.currency
|
||||||
|
? props.defaultValue.currency
|
||||||
|
: "LBC",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(newValues) {
|
||||||
|
const newState = Object.assign({}, this.state, newValues);
|
||||||
|
this.setState(newState);
|
||||||
|
this.props.onChange({
|
||||||
|
amount: newState.amount,
|
||||||
|
currency: newState.currency,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFeeAmountChange(event) {
|
||||||
|
this.handleChange({
|
||||||
|
amount: event.target.value ? Number(event.target.value) : null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFeeCurrencyChange(event) {
|
||||||
|
this.handleChange({ currency: event.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { defaultValue, placeholder, min, step } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="form-field">
|
||||||
|
<FormField
|
||||||
|
type="number"
|
||||||
|
name="amount"
|
||||||
|
min={min}
|
||||||
|
placeholder={placeholder || null}
|
||||||
|
step={step}
|
||||||
|
onChange={event => this.handleFeeAmountChange(event)}
|
||||||
|
defaultValue={
|
||||||
|
defaultValue && defaultValue.amount ? defaultValue.amount : ""
|
||||||
|
}
|
||||||
|
className="form-field__input--inline"
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
type="select"
|
||||||
|
name="currency"
|
||||||
|
onChange={event => this.handleFeeCurrencyChange(event)}
|
||||||
|
defaultValue={
|
||||||
|
defaultValue && defaultValue.currency ? defaultValue.currency : ""
|
||||||
|
}
|
||||||
|
className="form-field__input--inline"
|
||||||
|
>
|
||||||
|
<option value="LBC">{__("LBRY Credits (LBC)")}</option>
|
||||||
|
<option value="USD">{__("US Dollars")}</option>
|
||||||
|
</FormField>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FormFieldPrice;
|
|
@ -1,12 +1,12 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import lbry from "lbry";
|
import { formatCredits } from "utils";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { selectBalance } from "selectors/wallet";
|
import { selectBalance } from "selectors/wallet";
|
||||||
import { doNavigate, doHistoryBack } from "actions/app";
|
import { doNavigate, doHistoryBack } from "actions/app";
|
||||||
import Header from "./view";
|
import Header from "./view";
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
balance: lbry.formatCredits(selectBalance(state), 1),
|
balance: formatCredits(selectBalance(state), 1),
|
||||||
publish: __("Publish"),
|
publish: __("Publish"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,19 @@ export const Header = props => {
|
||||||
return (
|
return (
|
||||||
<header id="header">
|
<header id="header">
|
||||||
<div className="header__item">
|
<div className="header__item">
|
||||||
<Link onClick={back} button="alt button--flat" icon="icon-arrow-left" />
|
<Link
|
||||||
|
onClick={back}
|
||||||
|
button="alt button--flat"
|
||||||
|
icon="icon-arrow-left"
|
||||||
|
title={__("Back")}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="header__item">
|
<div className="header__item">
|
||||||
<Link
|
<Link
|
||||||
onClick={() => navigate("/discover")}
|
onClick={() => navigate("/discover")}
|
||||||
button="alt button--flat"
|
button="alt button--flat"
|
||||||
icon="icon-home"
|
icon="icon-home"
|
||||||
|
title={__("Discover Content")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="header__item header__item--wunderbar">
|
<div className="header__item header__item--wunderbar">
|
||||||
|
@ -26,6 +32,7 @@ export const Header = props => {
|
||||||
button="text"
|
button="text"
|
||||||
icon="icon-bank"
|
icon="icon-bank"
|
||||||
label={balance}
|
label={balance}
|
||||||
|
title={__("Wallet")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="header__item">
|
<div className="header__item">
|
||||||
|
@ -41,6 +48,7 @@ export const Header = props => {
|
||||||
onClick={() => navigate("/downloaded")}
|
onClick={() => navigate("/downloaded")}
|
||||||
button="alt button--flat"
|
button="alt button--flat"
|
||||||
icon="icon-folder"
|
icon="icon-folder"
|
||||||
|
title={__("Downloads and Publishes")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="header__item">
|
<div className="header__item">
|
||||||
|
@ -48,6 +56,7 @@ export const Header = props => {
|
||||||
onClick={() => navigate("/settings")}
|
onClick={() => navigate("/settings")}
|
||||||
button="alt button--flat"
|
button="alt button--flat"
|
||||||
icon="icon-gear"
|
icon="icon-gear"
|
||||||
|
title={__("Settings")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
5
ui/js/component/iconFeatured/index.js
Normal file
5
ui/js/component/iconFeatured/index.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import IconFeatured from "./view";
|
||||||
|
|
||||||
|
export default connect(null, null)(IconFeatured);
|
15
ui/js/component/iconFeatured/view.jsx
Normal file
15
ui/js/component/iconFeatured/view.jsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Icon } from "component/common.js";
|
||||||
|
|
||||||
|
const IconFeatured = props => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className="icon-featured"
|
||||||
|
title={__("Watch content with this icon to earn weekly rewards.")}
|
||||||
|
>
|
||||||
|
<Icon icon="icon-rocket" fixed className="card__icon-featured-content" />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IconFeatured;
|
|
@ -11,7 +11,6 @@ const Link = props => {
|
||||||
icon,
|
icon,
|
||||||
badge,
|
badge,
|
||||||
button,
|
button,
|
||||||
hidden,
|
|
||||||
disabled,
|
disabled,
|
||||||
children,
|
children,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import ReactModal from "react-modal";
|
|
||||||
|
|
||||||
export class ModalPage extends React.PureComponent {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<ReactModal
|
|
||||||
onCloseRequested={this.props.onAborted || this.props.onConfirmed}
|
|
||||||
{...this.props}
|
|
||||||
className={(this.props.className || "") + " modal-page"}
|
|
||||||
overlayClassName="modal-overlay"
|
|
||||||
>
|
|
||||||
<div className="modal-page__content">
|
|
||||||
{this.props.children}
|
|
||||||
</div>
|
|
||||||
</ReactModal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ModalPage;
|
|
12
ui/js/component/modalAuthFailure/index.js
Normal file
12
ui/js/component/modalAuthFailure/index.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { doCloseModal } from "actions/app";
|
||||||
|
import ModalAuthFailure from "./view";
|
||||||
|
|
||||||
|
const select = state => ({});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
close: () => dispatch(doCloseModal()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(ModalAuthFailure);
|
31
ui/js/component/modalAuthFailure/view.jsx
Normal file
31
ui/js/component/modalAuthFailure/view.jsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Modal } from "component/modal";
|
||||||
|
|
||||||
|
class ModalAuthFailure extends React.PureComponent {
|
||||||
|
render() {
|
||||||
|
const { close } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={true}
|
||||||
|
contentLabel={__("Unable to Authenticate")}
|
||||||
|
type="confirm"
|
||||||
|
confirmButtonLabel={__("Reload")}
|
||||||
|
abortButtonLabel={__("Continue")}
|
||||||
|
onConfirmed={() => {
|
||||||
|
window.location.reload();
|
||||||
|
}}
|
||||||
|
onAborted={close}
|
||||||
|
>
|
||||||
|
<h3>{__("Authentication Failure")}</h3>
|
||||||
|
<p>
|
||||||
|
{__(
|
||||||
|
"If reloading does not fix this, or you see this at every start up, please email help@lbry.io."
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalAuthFailure;
|
|
@ -6,7 +6,7 @@ class ModalError extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const { modal, closeModal, error } = this.props;
|
const { modal, closeModal, error } = this.props;
|
||||||
|
|
||||||
const errorObj = typeof error === "string" ? { error: error } : error;
|
const errorObj = typeof error === "string" ? { message: error } : error;
|
||||||
|
|
||||||
const error_key_labels = {
|
const error_key_labels = {
|
||||||
connectionString: __("API connection string"),
|
connectionString: __("API connection string"),
|
||||||
|
@ -18,10 +18,10 @@ class ModalError extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
const errorInfoList = [];
|
const errorInfoList = [];
|
||||||
for (let key of Object.keys(error)) {
|
for (let key of Object.keys(errorObj)) {
|
||||||
let val = typeof error[key] == "string"
|
let val = typeof errorObj[key] == "string"
|
||||||
? error[key]
|
? errorObj[key]
|
||||||
: JSON.stringify(error[key]);
|
: JSON.stringify(errorObj[key]);
|
||||||
let label = error_key_labels[key];
|
let label = error_key_labels[key];
|
||||||
errorInfoList.push(
|
errorInfoList.push(
|
||||||
<li key={key}><strong>{label}</strong>: <code>{val}</code></li>
|
<li key={key}><strong>{label}</strong>: <code>{val}</code></li>
|
||||||
|
|
20
ui/js/component/modalFirstReward/index.js
Normal file
20
ui/js/component/modalFirstReward/index.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import React from "react";
|
||||||
|
import rewards from "rewards";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { doCloseModal } from "actions/app";
|
||||||
|
import { makeSelectRewardByType } from "selectors/rewards";
|
||||||
|
import ModalFirstReward from "./view";
|
||||||
|
|
||||||
|
const select = (state, props) => {
|
||||||
|
const selectReward = makeSelectRewardByType();
|
||||||
|
|
||||||
|
return {
|
||||||
|
reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
closeModal: () => dispatch(doCloseModal()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(ModalFirstReward);
|
49
ui/js/component/modalFirstReward/view.jsx
Normal file
49
ui/js/component/modalFirstReward/view.jsx
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Modal } from "component/modal";
|
||||||
|
import { CreditAmount } from "component/common";
|
||||||
|
|
||||||
|
class ModalFirstReward extends React.PureComponent {
|
||||||
|
render() {
|
||||||
|
const { closeModal, reward } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
type="alert"
|
||||||
|
overlayClassName="modal-overlay modal-overlay--clear"
|
||||||
|
isOpen={true}
|
||||||
|
contentLabel={__("Welcome to LBRY")}
|
||||||
|
onConfirmed={closeModal}
|
||||||
|
>
|
||||||
|
<section>
|
||||||
|
<h3 className="modal__header">{__("Your First Reward")}</h3>
|
||||||
|
<p>
|
||||||
|
{__("You just earned your first reward of")}
|
||||||
|
{" "}<CreditAmount amount={reward.reward_amount} />.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{__(
|
||||||
|
"This reward will show in your Wallet in the top right momentarily (if it hasn't already)."
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{__(
|
||||||
|
"These credits are used to compensate creators, to publish your own content, 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, please know that LBRY is an early beta and that it earns the name."
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalFirstReward;
|
12
ui/js/component/modalIncompatibleDaemon/index.js
Normal file
12
ui/js/component/modalIncompatibleDaemon/index.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { doQuit, doSkipWrongDaemonNotice } from "actions/app";
|
||||||
|
import ModalIncompatibleDaemon from "./view";
|
||||||
|
|
||||||
|
const select = state => ({});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
quit: () => dispatch(doQuit()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(ModalIncompatibleDaemon);
|
29
ui/js/component/modalIncompatibleDaemon/view.jsx
Normal file
29
ui/js/component/modalIncompatibleDaemon/view.jsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Modal } from "component/modal";
|
||||||
|
import Link from "component/link";
|
||||||
|
|
||||||
|
class ModalIncompatibleDaemon extends React.PureComponent {
|
||||||
|
render() {
|
||||||
|
const { quit } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={true}
|
||||||
|
contentLabel={__("Incompatible daemon running")}
|
||||||
|
type="alert"
|
||||||
|
confirmButtonLabel={__("Quit")}
|
||||||
|
onConfirmed={quit}
|
||||||
|
>
|
||||||
|
{__(
|
||||||
|
"This browser is running with an incompatible version of the LBRY protocol and your install must be repaired. "
|
||||||
|
)}
|
||||||
|
<Link
|
||||||
|
label={__("Learn more")}
|
||||||
|
href="https://lbry.io/faq/incompatible-protocol-version"
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalIncompatibleDaemon;
|
16
ui/js/component/modalInsufficientCredits/index.js
Normal file
16
ui/js/component/modalInsufficientCredits/index.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { doCloseModal, doNavigate } from "actions/app";
|
||||||
|
import ModalInsufficientCredits from "./view";
|
||||||
|
|
||||||
|
const select = state => ({});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
addFunds: () => {
|
||||||
|
dispatch(doNavigate("/rewards"));
|
||||||
|
dispatch(doCloseModal());
|
||||||
|
},
|
||||||
|
closeModal: () => dispatch(doCloseModal()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(ModalInsufficientCredits);
|
24
ui/js/component/modalInsufficientCredits/view.jsx
Normal file
24
ui/js/component/modalInsufficientCredits/view.jsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Modal } from "component/modal";
|
||||||
|
|
||||||
|
class ModalInsufficientCredits extends React.PureComponent {
|
||||||
|
render() {
|
||||||
|
const { addFunds, closeModal } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={true}
|
||||||
|
type="confirm"
|
||||||
|
contentLabel={__("Not enough credits")}
|
||||||
|
confirmButtonLabel={__("Get Credits")}
|
||||||
|
abortButtonLabel={__("Cancel")}
|
||||||
|
onAborted={closeModal}
|
||||||
|
onConfirmed={addFunds}
|
||||||
|
>
|
||||||
|
{__("More LBRY credits are required to purchase this.")}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalInsufficientCredits;
|
|
@ -1,8 +1,9 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { doCloseModal, doHistoryBack } from "actions/app";
|
import { doCloseModal, doHistoryBack } from "actions/app";
|
||||||
import { doDeleteFile } from "actions/file_info";
|
import { doDeleteFileAndGoBack } from "actions/file_info";
|
||||||
import { makeSelectClaimForUriIsMine } from "selectors/claims";
|
import { makeSelectClaimForUriIsMine } from "selectors/claims";
|
||||||
|
import batchActions from "util/batchActions";
|
||||||
|
|
||||||
import ModalRemoveFile from "./view";
|
import ModalRemoveFile from "./view";
|
||||||
|
|
||||||
|
@ -19,8 +20,7 @@ const makeSelect = () => {
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
closeModal: () => dispatch(doCloseModal()),
|
closeModal: () => dispatch(doCloseModal()),
|
||||||
deleteFile: (fileInfo, deleteFromComputer, abandonClaim) => {
|
deleteFile: (fileInfo, deleteFromComputer, abandonClaim) => {
|
||||||
dispatch(doHistoryBack());
|
dispatch(doDeleteFileAndGoBack(fileInfo, deleteFromComputer, abandonClaim));
|
||||||
dispatch(doDeleteFile(fileInfo, deleteFromComputer, abandonClaim));
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Modal } from "component/modal";
|
import { Modal } from "component/modal";
|
||||||
import { FormField } from "component/form.js";
|
import FormField from "component/formField";
|
||||||
|
|
||||||
class ModalRemoveFile extends React.PureComponent {
|
class ModalRemoveFile extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Modal } from "component/modal";
|
import { Modal } from "component/modal";
|
||||||
import { downloadUpgrade, skipUpgrade } from "actions/app";
|
|
||||||
|
|
||||||
class ModalUpgrade extends React.PureComponent {
|
class ModalUpgrade extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -1,28 +1,40 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import rewards from "rewards";
|
import rewards from "rewards";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { doCloseModal } from "actions/app";
|
import { doCloseModal, doAuthNavigate } from "actions/app";
|
||||||
|
import { doSetClientSetting } from "actions/settings";
|
||||||
import { selectUserIsRewardApproved } from "selectors/user";
|
import { selectUserIsRewardApproved } from "selectors/user";
|
||||||
import {
|
import {
|
||||||
makeSelectHasClaimedReward,
|
makeSelectHasClaimedReward,
|
||||||
makeSelectClaimRewardError,
|
|
||||||
makeSelectRewardByType,
|
makeSelectRewardByType,
|
||||||
|
selectTotalRewardValue,
|
||||||
} from "selectors/rewards";
|
} from "selectors/rewards";
|
||||||
import WelcomeModal from "./view";
|
import ModalWelcome from "./view";
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
const selectHasClaimed = makeSelectHasClaimedReward(),
|
const selectHasClaimed = makeSelectHasClaimedReward(),
|
||||||
selectReward = makeSelectRewardByType();
|
selectReward = makeSelectRewardByType();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasClaimed: selectHasClaimed(state, { reward_type: rewards.TYPE_NEW_USER }),
|
|
||||||
isRewardApproved: selectUserIsRewardApproved(state),
|
isRewardApproved: selectUserIsRewardApproved(state),
|
||||||
reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }),
|
reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }),
|
||||||
|
totalRewardValue: selectTotalRewardValue(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => () => {
|
||||||
closeModal: () => dispatch(doCloseModal()),
|
const closeModal = () => {
|
||||||
});
|
dispatch(doSetClientSetting("welcome_acknowledged", true));
|
||||||
|
dispatch(doCloseModal());
|
||||||
|
};
|
||||||
|
|
||||||
export default connect(select, perform)(WelcomeModal);
|
return {
|
||||||
|
verifyAccount: () => {
|
||||||
|
closeModal();
|
||||||
|
dispatch(doAuthNavigate("/discover"));
|
||||||
|
},
|
||||||
|
closeModal: closeModal,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(select, perform)(ModalWelcome);
|
||||||
|
|
|
@ -4,14 +4,24 @@ import { CreditAmount } from "component/common";
|
||||||
import Link from "component/link";
|
import Link from "component/link";
|
||||||
import RewardLink from "component/rewardLink";
|
import RewardLink from "component/rewardLink";
|
||||||
|
|
||||||
class WelcomeModal extends React.PureComponent {
|
class ModalWelcome extends React.PureComponent {
|
||||||
render() {
|
constructor(props) {
|
||||||
const { closeModal, hasClaimed, isRewardApproved, reward } = this.props;
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isFirstScreen: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return !hasClaimed
|
render() {
|
||||||
? <Modal type="custom" isOpen={true} contentLabel="Welcome to LBRY">
|
const { closeModal, totalRewardValue, verifyAccount } = this.props;
|
||||||
|
|
||||||
|
const totalRewardRounded = Math.round(totalRewardValue / 10) * 10;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal type="custom" isOpen={true} contentLabel="Welcome to LBRY">
|
||||||
|
{this.state.isFirstScreen &&
|
||||||
<section>
|
<section>
|
||||||
<h3 className="modal__header">{__("Welcome to LBRY.")}</h3>
|
<h3 className="modal__header">{__("Welcome to LBRY")}</h3>
|
||||||
<p>
|
<p>
|
||||||
{__(
|
{__(
|
||||||
"Using LBRY is like dating a centaur. Totally normal up top, and"
|
"Using LBRY is like dating a centaur. Totally normal up top, and"
|
||||||
|
@ -24,58 +34,51 @@ class WelcomeModal extends React.PureComponent {
|
||||||
"Below, LBRY is controlled by users -- you -- via blockchain and decentralization."
|
"Below, LBRY is controlled by users -- you -- via blockchain and decentralization."
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<div className="modal__buttons">
|
||||||
{__("Thank you for making content freedom possible!")}
|
<Link
|
||||||
{" "}{isRewardApproved ? __("Here's a nickel, kid.") : ""}
|
button="primary"
|
||||||
</p>
|
onClick={() => {
|
||||||
<div className="text-center">
|
this.setState({ isFirstScreen: false });
|
||||||
{isRewardApproved
|
}}
|
||||||
? <RewardLink reward_type="new_user" button="primary" />
|
label={__("Continue")}
|
||||||
: <Link
|
/>
|
||||||
button="primary"
|
|
||||||
onClick={closeModal}
|
|
||||||
label={__("Continue")}
|
|
||||||
/>}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>}
|
||||||
</Modal>
|
{!this.state.isFirstScreen &&
|
||||||
: <Modal
|
|
||||||
type="alert"
|
|
||||||
overlayClassName="modal-overlay modal-overlay--clear"
|
|
||||||
isOpen={true}
|
|
||||||
contentLabel={__("Welcome to LBRY")}
|
|
||||||
onConfirmed={closeModal}
|
|
||||||
>
|
|
||||||
<section>
|
<section>
|
||||||
<h3 className="modal__header">{__("About Your Reward")}</h3>
|
<h3 className="modal__header">{__("Claim Your Credits")}</h3>
|
||||||
<p>
|
<p>
|
||||||
{__("You earned a reward of")}
|
The LBRY network is controlled and powered by credits called{" "}
|
||||||
{" "}<CreditAmount amount={reward.reward_amount} label={false} />
|
<em>LBC</em>, a blockchain asset.
|
||||||
{" "}{__("LBRY credits, or")} <em>{__("LBC")}</em>.
|
</p>
|
||||||
|
<p>
|
||||||
|
{__("New patrons receive ")} {" "}
|
||||||
|
{totalRewardValue
|
||||||
|
? <CreditAmount amount={totalRewardRounded} />
|
||||||
|
: <span className="credit-amount">{__("credits")}</span>}
|
||||||
|
{" "} {__("in rewards for usage and influence of the network.")}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{__(
|
{__(
|
||||||
"This reward will show in your Wallet momentarily, probably while you are reading this message."
|
"You'll also earn weekly bonuses for checking out the greatest new stuff."
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<div className="modal__buttons">
|
||||||
{__(
|
<Link
|
||||||
"LBC is used to compensate creators, to publish, and to have say in how the network works."
|
button="primary"
|
||||||
)}
|
onClick={verifyAccount}
|
||||||
</p>
|
label={__("You Had Me At Free LBC")}
|
||||||
<p>
|
/>
|
||||||
{__(
|
<Link
|
||||||
"No need to understand it all just yet! Try watching or downloading something next."
|
button="alt"
|
||||||
)}
|
onClick={closeModal}
|
||||||
</p>
|
label={__("I Burn Money")}
|
||||||
<p>
|
/>
|
||||||
{__(
|
</div>
|
||||||
"Finally, know that LBRY is an early beta and that it earns the name."
|
</section>}
|
||||||
)}
|
</Modal>
|
||||||
</p>
|
);
|
||||||
</section>
|
|
||||||
</Modal>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WelcomeModal;
|
export default ModalWelcome;
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export class Notice extends React.PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
isError: React.PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
isError: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<section
|
|
||||||
className={
|
|
||||||
"notice " +
|
|
||||||
(this.props.isError ? "notice--error " : "") +
|
|
||||||
(this.props.className || "")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{this.props.children}
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Notice;
|
|
5
ui/js/component/publishForm/index.js
Normal file
5
ui/js/component/publishForm/index.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import PublishForm from "./view";
|
||||||
|
|
||||||
|
export default connect(null, null)(PublishForm);
|
177
ui/js/component/publishForm/internal/channelSection.jsx
Normal file
177
ui/js/component/publishForm/internal/channelSection.jsx
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
import React from "react";
|
||||||
|
import lbryuri from "lbryuri";
|
||||||
|
import { FormRow } from "component/form.js";
|
||||||
|
import { BusyMessage } from "component/common";
|
||||||
|
import Link from "component/link";
|
||||||
|
|
||||||
|
class ChannelSection extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
newChannelName: "@",
|
||||||
|
newChannelBid: 10,
|
||||||
|
addingChannel: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChannelChange(event) {
|
||||||
|
const channel = event.target.value;
|
||||||
|
if (channel === "new") this.setState({ addingChannel: true });
|
||||||
|
else {
|
||||||
|
this.setState({ addingChannel: false });
|
||||||
|
this.props.handleChannelChange(event.target.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNewChannelNameChange(event) {
|
||||||
|
const newChannelName = event.target.value.startsWith("@")
|
||||||
|
? event.target.value
|
||||||
|
: "@" + event.target.value;
|
||||||
|
|
||||||
|
if (
|
||||||
|
newChannelName.length > 1 &&
|
||||||
|
!lbryuri.isValidName(newChannelName.substr(1), false)
|
||||||
|
) {
|
||||||
|
this.refs.newChannelName.showError(
|
||||||
|
__("LBRY channel names must contain only letters, numbers and dashes.")
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.refs.newChannelName.clearError();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
newChannelName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNewChannelBidChange(event) {
|
||||||
|
this.setState({
|
||||||
|
newChannelBid: event.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCreateChannelClick(event) {
|
||||||
|
if (this.state.newChannelName.length < 5) {
|
||||||
|
this.refs.newChannelName.showError(
|
||||||
|
__("LBRY channel names must be at least 4 characters in length.")
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
creatingChannel: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const newChannelName = this.state.newChannelName;
|
||||||
|
const amount = parseFloat(this.state.newChannelBid);
|
||||||
|
this.setState({
|
||||||
|
creatingChannel: true,
|
||||||
|
});
|
||||||
|
const success = () => {
|
||||||
|
this.setState({
|
||||||
|
creatingChannel: false,
|
||||||
|
addingChannel: false,
|
||||||
|
channel: newChannelName,
|
||||||
|
});
|
||||||
|
this.props.handleChannelChange(newChannelName);
|
||||||
|
};
|
||||||
|
const failure = err => {
|
||||||
|
this.setState({
|
||||||
|
creatingChannel: false,
|
||||||
|
});
|
||||||
|
this.refs.newChannelName.showError(
|
||||||
|
__("Unable to create channel due to an internal error.")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
this.props.createChannel(newChannelName, amount).then(success, failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const lbcInputHelp = __(
|
||||||
|
"This LBC remains yours and the deposit can be undone at any time."
|
||||||
|
);
|
||||||
|
|
||||||
|
const channel = this.state.addingChannel ? "new" : this.props.channel;
|
||||||
|
const { fetchingChannels, channels = [] } = this.props;
|
||||||
|
|
||||||
|
let channelContent = [];
|
||||||
|
channelContent.push(
|
||||||
|
<FormRow
|
||||||
|
key="channel"
|
||||||
|
type="select"
|
||||||
|
tabIndex="1"
|
||||||
|
onChange={this.handleChannelChange.bind(this)}
|
||||||
|
value={channel}
|
||||||
|
>
|
||||||
|
<option key="anonymous" value="anonymous">
|
||||||
|
{__("Anonymous")}
|
||||||
|
</option>
|
||||||
|
{this.props.channels.map(({ name }) =>
|
||||||
|
<option key={name} value={name}>{name}</option>
|
||||||
|
)}
|
||||||
|
<option key="new" value="new">
|
||||||
|
{__("New channel...")}
|
||||||
|
</option>
|
||||||
|
</FormRow>
|
||||||
|
);
|
||||||
|
if (fetchingChannels) {
|
||||||
|
channelContent.push(
|
||||||
|
<BusyMessage message="Updating channels" key="loading" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="card">
|
||||||
|
<div className="card__title-primary">
|
||||||
|
<h4>{__("Channel Name")}</h4>
|
||||||
|
<div className="card__subtitle">
|
||||||
|
{__(
|
||||||
|
"This is a username or handle that your content can be found under."
|
||||||
|
)}
|
||||||
|
{" "}
|
||||||
|
{__("Ex. @Marvel, @TheBeatles, @BooksByJoe")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
{channelContent}
|
||||||
|
</div>
|
||||||
|
{this.state.addingChannel &&
|
||||||
|
<div className="card__content">
|
||||||
|
<FormRow
|
||||||
|
label={__("Name")}
|
||||||
|
type="text"
|
||||||
|
onChange={this.handleNewChannelNameChange.bind(this)}
|
||||||
|
value={this.state.newChannelName}
|
||||||
|
/>
|
||||||
|
<FormRow
|
||||||
|
label={__("Deposit")}
|
||||||
|
postfix="LBC"
|
||||||
|
step="0.1"
|
||||||
|
min="0"
|
||||||
|
type="number"
|
||||||
|
helper={lbcInputHelp}
|
||||||
|
ref="newChannelName"
|
||||||
|
onChange={this.handleNewChannelBidChange.bind(this)}
|
||||||
|
value={this.state.newChannelBid}
|
||||||
|
/>
|
||||||
|
<div className="form-row-submit">
|
||||||
|
<Link
|
||||||
|
button="primary"
|
||||||
|
label={
|
||||||
|
!this.state.creatingChannel
|
||||||
|
? __("Create channel")
|
||||||
|
: __("Creating channel...")
|
||||||
|
}
|
||||||
|
onClick={this.handleCreateChannelClick.bind(this)}
|
||||||
|
disabled={this.state.creatingChannel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChannelSection;
|
926
ui/js/component/publishForm/view.jsx
Normal file
926
ui/js/component/publishForm/view.jsx
Normal file
|
@ -0,0 +1,926 @@
|
||||||
|
import React from "react";
|
||||||
|
import lbry from "lbry";
|
||||||
|
import lbryuri from "lbryuri";
|
||||||
|
import FormField from "component/formField";
|
||||||
|
import { FormRow } from "component/form.js";
|
||||||
|
import Link from "component/link";
|
||||||
|
import FormFieldPrice from "component/formFieldPrice";
|
||||||
|
import Modal from "component/modal";
|
||||||
|
import { BusyMessage } from "component/common";
|
||||||
|
import ChannelSection from "./internal/channelSection";
|
||||||
|
|
||||||
|
class PublishForm extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this._requiredFields = ["name", "bid", "meta_title", "tosAgree"];
|
||||||
|
|
||||||
|
this._defaultCopyrightNotice = "All rights reserved.";
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
rawName: "",
|
||||||
|
name: "",
|
||||||
|
bid: 10,
|
||||||
|
hasFile: false,
|
||||||
|
feeAmount: "",
|
||||||
|
feeCurrency: "LBC",
|
||||||
|
channel: "anonymous",
|
||||||
|
newChannelName: "@",
|
||||||
|
newChannelBid: 10,
|
||||||
|
meta_title: "",
|
||||||
|
meta_thumbnail: "",
|
||||||
|
meta_description: "",
|
||||||
|
meta_language: "en",
|
||||||
|
meta_nsfw: "0",
|
||||||
|
licenseType: "",
|
||||||
|
copyrightNotice: this._defaultCopyrightNotice,
|
||||||
|
otherLicenseDescription: "",
|
||||||
|
otherLicenseUrl: "",
|
||||||
|
tosAgree: false,
|
||||||
|
prefillDone: false,
|
||||||
|
uploadProgress: 0.0,
|
||||||
|
uploaded: false,
|
||||||
|
errorMessage: null,
|
||||||
|
submitting: false,
|
||||||
|
creatingChannel: false,
|
||||||
|
modal: null,
|
||||||
|
isFee: false,
|
||||||
|
customUrl: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateChannelList(channel) {
|
||||||
|
const { fetchingChannels, fetchChannelListMine } = this.props;
|
||||||
|
|
||||||
|
if (!fetchingChannels) fetchChannelListMine();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit(event) {
|
||||||
|
if (typeof event !== "undefined") {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
submitting: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let checkFields = this._requiredFields;
|
||||||
|
if (!this.myClaimExists()) {
|
||||||
|
checkFields.unshift("file");
|
||||||
|
}
|
||||||
|
|
||||||
|
let missingFieldFound = false;
|
||||||
|
for (let fieldName of checkFields) {
|
||||||
|
const field = this.refs[fieldName];
|
||||||
|
if (field) {
|
||||||
|
if (field.getValue() === "" || field.getValue() === false) {
|
||||||
|
field.showRequiredError();
|
||||||
|
if (!missingFieldFound) {
|
||||||
|
field.focus();
|
||||||
|
missingFieldFound = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
field.clearError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingFieldFound) {
|
||||||
|
this.setState({
|
||||||
|
submitting: false,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let metadata = {};
|
||||||
|
|
||||||
|
for (let metaField of ["title", "description", "thumbnail", "language"]) {
|
||||||
|
const value = this.state["meta_" + metaField];
|
||||||
|
if (value) {
|
||||||
|
metadata[metaField] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata.license = this.getLicense();
|
||||||
|
metadata.licenseUrl = this.getLicenseUrl();
|
||||||
|
metadata.nsfw = !!parseInt(this.state.meta_nsfw);
|
||||||
|
|
||||||
|
var doPublish = () => {
|
||||||
|
var publishArgs = {
|
||||||
|
name: this.state.name,
|
||||||
|
bid: parseFloat(this.state.bid),
|
||||||
|
metadata: metadata,
|
||||||
|
...(this.state.channel != "new" && this.state.channel != "anonymous"
|
||||||
|
? { channel_name: this.state.channel }
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.refs.file.getValue() !== "") {
|
||||||
|
publishArgs.file_path = this.refs.file.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
const success = claim => {};
|
||||||
|
const failure = error => this.handlePublishError(error);
|
||||||
|
|
||||||
|
this.handlePublishStarted();
|
||||||
|
this.props.publish(publishArgs).then(success, failure);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.state.isFee && parseFloat(this.state.feeAmount) > 0) {
|
||||||
|
lbry.wallet_unused_address().then(address => {
|
||||||
|
metadata.fee = {
|
||||||
|
currency: this.state.feeCurrency,
|
||||||
|
amount: parseFloat(this.state.feeAmount),
|
||||||
|
address: address,
|
||||||
|
};
|
||||||
|
|
||||||
|
doPublish();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
doPublish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePublishStarted() {
|
||||||
|
this.setState({
|
||||||
|
modal: "publishStarted",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePublishStartedConfirmed() {
|
||||||
|
this.props.navigate("/published");
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePublishError(error) {
|
||||||
|
this.setState({
|
||||||
|
submitting: false,
|
||||||
|
modal: "error",
|
||||||
|
errorMessage: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
claim() {
|
||||||
|
const { claimsByUri } = this.props;
|
||||||
|
const { uri } = this.state;
|
||||||
|
|
||||||
|
return claimsByUri[uri];
|
||||||
|
}
|
||||||
|
|
||||||
|
topClaimValue() {
|
||||||
|
if (!this.claim()) return null;
|
||||||
|
|
||||||
|
return parseFloat(this.claim().amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
myClaimExists() {
|
||||||
|
const { myClaims } = this.props;
|
||||||
|
const { name } = this.state;
|
||||||
|
|
||||||
|
if (!name) return false;
|
||||||
|
|
||||||
|
return !!myClaims.find(claim => claim.name === name);
|
||||||
|
}
|
||||||
|
|
||||||
|
topClaimIsMine() {
|
||||||
|
const myClaimInfo = this.myClaimInfo();
|
||||||
|
const { claimsByUri } = this.props;
|
||||||
|
const { uri } = this.state;
|
||||||
|
|
||||||
|
if (!uri) return null;
|
||||||
|
|
||||||
|
const claim = claimsByUri[uri];
|
||||||
|
|
||||||
|
if (!claim) return true;
|
||||||
|
if (!myClaimInfo) return false;
|
||||||
|
|
||||||
|
return myClaimInfo.amount >= claim.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
myClaimInfo() {
|
||||||
|
const { name } = this.state;
|
||||||
|
|
||||||
|
return Object.values(this.props.myClaims).find(
|
||||||
|
claim => claim.name === name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNameChange(event) {
|
||||||
|
var rawName = event.target.value;
|
||||||
|
this.setState({
|
||||||
|
customUrl: Boolean(rawName.length),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.nameChanged(rawName);
|
||||||
|
}
|
||||||
|
|
||||||
|
nameChanged(rawName) {
|
||||||
|
if (!rawName) {
|
||||||
|
this.setState({
|
||||||
|
rawName: "",
|
||||||
|
name: "",
|
||||||
|
uri: "",
|
||||||
|
prefillDone: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lbryuri.isValidName(rawName, false)) {
|
||||||
|
this.refs.name.showError(
|
||||||
|
__("LBRY names must contain only letters, numbers and dashes.")
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel = "";
|
||||||
|
if (this.state.channel !== "anonymous") channel = this.state.channel;
|
||||||
|
|
||||||
|
const name = rawName.toLowerCase();
|
||||||
|
const uri = lbryuri.build({ contentName: name, channelName: channel });
|
||||||
|
this.setState({
|
||||||
|
rawName: rawName,
|
||||||
|
name: name,
|
||||||
|
prefillDone: false,
|
||||||
|
uri,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.resolveUriTimeout) {
|
||||||
|
clearTimeout(this.resolveUriTimeout);
|
||||||
|
this.resolveUriTimeout = undefined;
|
||||||
|
}
|
||||||
|
const resolve = () => this.props.resolveUri(uri);
|
||||||
|
|
||||||
|
this.resolveUriTimeout = setTimeout(resolve.bind(this), 500, {
|
||||||
|
once: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePrefillClicked() {
|
||||||
|
const claimInfo = this.myClaimInfo();
|
||||||
|
const {
|
||||||
|
license,
|
||||||
|
licenseUrl,
|
||||||
|
title,
|
||||||
|
thumbnail,
|
||||||
|
description,
|
||||||
|
language,
|
||||||
|
nsfw,
|
||||||
|
} = claimInfo.value.stream.metadata;
|
||||||
|
|
||||||
|
let newState = {
|
||||||
|
meta_title: title,
|
||||||
|
meta_thumbnail: thumbnail,
|
||||||
|
meta_description: description,
|
||||||
|
meta_language: language,
|
||||||
|
meta_nsfw: nsfw,
|
||||||
|
prefillDone: true,
|
||||||
|
bid: claimInfo.amount,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (license == this._defaultCopyrightNotice) {
|
||||||
|
newState.licenseType = "copyright";
|
||||||
|
newState.copyrightNotice = this._defaultCopyrightNotice;
|
||||||
|
} else {
|
||||||
|
// If the license URL or description matches one of the drop-down options, use that
|
||||||
|
let licenseType = "other"; // Will be overridden if we find a match
|
||||||
|
for (let option of this._meta_license.getOptions()) {
|
||||||
|
if (
|
||||||
|
option.getAttribute("data-url") === licenseUrl ||
|
||||||
|
option.text === license
|
||||||
|
) {
|
||||||
|
licenseType = option.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (licenseType == "other") {
|
||||||
|
newState.otherLicenseDescription = license;
|
||||||
|
newState.otherLicenseUrl = licenseUrl;
|
||||||
|
}
|
||||||
|
newState.licenseType = licenseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBidChange(event) {
|
||||||
|
this.setState({
|
||||||
|
bid: event.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFeeChange(newValue) {
|
||||||
|
this.setState({
|
||||||
|
feeAmount: newValue.amount,
|
||||||
|
feeCurrency: newValue.currency,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFeePrefChange(feeEnabled) {
|
||||||
|
this.setState({
|
||||||
|
isFee: feeEnabled,
|
||||||
|
feeAmount: this.state.feeAmount == "" ? "5.00" : this.state.feeAmount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMetadataChange(event) {
|
||||||
|
/**
|
||||||
|
* This function is used for all metadata inputs that store the final value directly into state.
|
||||||
|
* The only exceptions are inputs related to license description and license URL, which require
|
||||||
|
* more complex logic and the final value is determined at submit time.
|
||||||
|
*/
|
||||||
|
this.setState({
|
||||||
|
["meta_" + event.target.name]: event.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDescriptionChanged(text) {
|
||||||
|
this.setState({
|
||||||
|
meta_description: text,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLicenseTypeChange(event) {
|
||||||
|
this.setState({
|
||||||
|
licenseType: event.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCopyrightNoticeChange(event) {
|
||||||
|
this.setState({
|
||||||
|
copyrightNotice: event.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOtherLicenseDescriptionChange(event) {
|
||||||
|
this.setState({
|
||||||
|
otherLicenseDescription: event.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOtherLicenseUrlChange(event) {
|
||||||
|
this.setState({
|
||||||
|
otherLicenseUrl: event.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChannelChange(channelName) {
|
||||||
|
this.setState({
|
||||||
|
channel: channelName,
|
||||||
|
});
|
||||||
|
const nameChanged = () => this.nameChanged(this.state.rawName);
|
||||||
|
setTimeout(nameChanged.bind(this), 500, { once: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTOSChange(event) {
|
||||||
|
this.setState({
|
||||||
|
tosAgree: event.target.checked,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCreateChannelClick(event) {
|
||||||
|
if (this.state.newChannelName.length < 5) {
|
||||||
|
this.refs.newChannelName.showError(
|
||||||
|
__("LBRY channel names must be at least 4 characters in length.")
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
creatingChannel: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const newChannelName = this.state.newChannelName;
|
||||||
|
lbry
|
||||||
|
.channel_new({
|
||||||
|
channel_name: newChannelName,
|
||||||
|
amount: parseFloat(this.state.newChannelBid),
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setState({
|
||||||
|
creatingChannel: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._updateChannelList(newChannelName);
|
||||||
|
}, 10000);
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
// TODO: better error handling
|
||||||
|
this.refs.newChannelName.showError(
|
||||||
|
__("Unable to create channel due to an internal error.")
|
||||||
|
);
|
||||||
|
this.setState({
|
||||||
|
creatingChannel: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLicense() {
|
||||||
|
switch (this.state.licenseType) {
|
||||||
|
case "copyright":
|
||||||
|
return this.state.copyrightNotice;
|
||||||
|
case "other":
|
||||||
|
return this.state.otherLicenseDescription;
|
||||||
|
default:
|
||||||
|
return this._meta_license.getSelectedElement().text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLicenseUrl() {
|
||||||
|
switch (this.state.licenseType) {
|
||||||
|
case "copyright":
|
||||||
|
return "";
|
||||||
|
case "other":
|
||||||
|
return this.state.otherLicenseUrl;
|
||||||
|
default:
|
||||||
|
return this._meta_license.getSelectedElement().getAttribute("data-url");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.fetchClaimListMine();
|
||||||
|
this._updateChannelList();
|
||||||
|
}
|
||||||
|
|
||||||
|
onFileChange() {
|
||||||
|
if (this.refs.file.getValue()) {
|
||||||
|
this.setState({ hasFile: true });
|
||||||
|
if (!this.state.customUrl) {
|
||||||
|
let fileName = this._getFileName(this.refs.file.getValue());
|
||||||
|
this.nameChanged(fileName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setState({ hasFile: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getFileName(fileName) {
|
||||||
|
const path = require("path");
|
||||||
|
const extension = path.extname(fileName);
|
||||||
|
|
||||||
|
fileName = path.basename(fileName, extension);
|
||||||
|
fileName = fileName.replace(lbryuri.REGEXP_INVALID_URI, "");
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNameBidHelpText() {
|
||||||
|
if (this.state.prefillDone) {
|
||||||
|
return __("Existing claim data was prefilled");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.state.uri &&
|
||||||
|
this.props.resolvingUris.indexOf(this.state.uri) !== -1 &&
|
||||||
|
this.claim() === undefined
|
||||||
|
) {
|
||||||
|
return __("Checking...");
|
||||||
|
} else if (!this.state.name) {
|
||||||
|
return __("Select a URL for this publish.");
|
||||||
|
} else if (!this.claim()) {
|
||||||
|
return __("This URL is unused.");
|
||||||
|
} else if (this.myClaimExists() && !this.state.prefillDone) {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{__("You already have a claim with this name.")}{" "}
|
||||||
|
<Link
|
||||||
|
label={__("Use data from my existing claim")}
|
||||||
|
onClick={() => this.handlePrefillClicked()}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else if (this.claim()) {
|
||||||
|
if (this.topClaimValue() === 1) {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{__(
|
||||||
|
'A deposit of at least one credit is required to win "%s". However, you can still get a permanent URL for any amount.',
|
||||||
|
this.state.name
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{__(
|
||||||
|
'A deposit of at least "%s" credits is required to win "%s". However, you can still get a permanent URL for any amount.',
|
||||||
|
this.topClaimValue(),
|
||||||
|
this.state.name
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeModal() {
|
||||||
|
this.setState({
|
||||||
|
modal: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const lbcInputHelp = __(
|
||||||
|
"This LBC remains yours and the deposit can be undone at any time."
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="main--single-column">
|
||||||
|
<form
|
||||||
|
onSubmit={event => {
|
||||||
|
this.handleSubmit(event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<section className="card">
|
||||||
|
<div className="card__title-primary">
|
||||||
|
<h4>{__("Content")}</h4>
|
||||||
|
<div className="card__subtitle">
|
||||||
|
{__("What are you publishing?")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
<FormRow
|
||||||
|
name="file"
|
||||||
|
label="File"
|
||||||
|
ref="file"
|
||||||
|
type="file"
|
||||||
|
onChange={event => {
|
||||||
|
this.onFileChange(event);
|
||||||
|
}}
|
||||||
|
helper={
|
||||||
|
this.myClaimExists()
|
||||||
|
? __(
|
||||||
|
"If you don't choose a file, the file from your existing claim will be used."
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{!this.state.hasFile && !this.myClaimExists()
|
||||||
|
? null
|
||||||
|
: <div>
|
||||||
|
<div className="card__content">
|
||||||
|
<FormRow
|
||||||
|
label={__("Title")}
|
||||||
|
type="text"
|
||||||
|
name="title"
|
||||||
|
value={this.state.meta_title}
|
||||||
|
placeholder="Titular Title"
|
||||||
|
onChange={event => {
|
||||||
|
this.handleMetadataChange(event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
<FormRow
|
||||||
|
type="text"
|
||||||
|
label={__("Thumbnail URL")}
|
||||||
|
name="thumbnail"
|
||||||
|
value={this.state.meta_thumbnail}
|
||||||
|
placeholder="http://spee.ch/mylogo"
|
||||||
|
onChange={event => {
|
||||||
|
this.handleMetadataChange(event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
<FormRow
|
||||||
|
label={__("Description")}
|
||||||
|
type="SimpleMDE"
|
||||||
|
ref="meta_description"
|
||||||
|
name="description"
|
||||||
|
value={this.state.meta_description}
|
||||||
|
placeholder={__("Description of your content")}
|
||||||
|
onChange={text => {
|
||||||
|
this.handleDescriptionChanged(text);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
<FormRow
|
||||||
|
label={__("Language")}
|
||||||
|
type="select"
|
||||||
|
value={this.state.meta_language}
|
||||||
|
name="language"
|
||||||
|
onChange={event => {
|
||||||
|
this.handleMetadataChange(event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="en">{__("English")}</option>
|
||||||
|
<option value="zh">{__("Chinese")}</option>
|
||||||
|
<option value="fr">{__("French")}</option>
|
||||||
|
<option value="de">{__("German")}</option>
|
||||||
|
<option value="jp">{__("Japanese")}</option>
|
||||||
|
<option value="ru">{__("Russian")}</option>
|
||||||
|
<option value="es">{__("Spanish")}</option>
|
||||||
|
</FormRow>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
<FormRow
|
||||||
|
type="select"
|
||||||
|
label={__("Maturity")}
|
||||||
|
value={this.state.meta_nsfw}
|
||||||
|
name="nsfw"
|
||||||
|
onChange={event => {
|
||||||
|
this.handleMetadataChange(event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* <option value=""></option> */}
|
||||||
|
<option value="0">{__("All Ages")}</option>
|
||||||
|
<option value="1">{__("Adults Only")}</option>
|
||||||
|
</FormRow>
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="card">
|
||||||
|
<div className="card__title-primary">
|
||||||
|
<h4>{__("Access")}</h4>
|
||||||
|
<div className="card__subtitle">
|
||||||
|
{__("How much does this content cost?")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
<div className="form-row__label-row">
|
||||||
|
<label className="form-row__label">{__("Price")}</label>
|
||||||
|
</div>
|
||||||
|
<FormRow
|
||||||
|
label={__("Free")}
|
||||||
|
type="radio"
|
||||||
|
name="isFree"
|
||||||
|
onChange={() => this.handleFeePrefChange(false)}
|
||||||
|
checked={!this.state.isFee}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
type="radio"
|
||||||
|
name="isFree"
|
||||||
|
label={!this.state.isFee ? __("Choose price...") : __("Price ")}
|
||||||
|
onChange={() => {
|
||||||
|
this.handleFeePrefChange(true);
|
||||||
|
}}
|
||||||
|
checked={this.state.isFee}
|
||||||
|
/>
|
||||||
|
<span className={!this.state.isFee ? "hidden" : ""}>
|
||||||
|
{/*min=0.01 caused weird interactions with step (e.g. down from 5 equals 4.91 rather than 4.9) */}
|
||||||
|
<FormFieldPrice
|
||||||
|
min="0"
|
||||||
|
step="0.1"
|
||||||
|
defaultValue={{ amount: 5.0, currency: "LBC" }}
|
||||||
|
onChange={val => this.handleFeeChange(val)}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
{this.state.isFee && this.state.feeCurrency.toUpperCase() != "LBC"
|
||||||
|
? <div className="form-field__helper">
|
||||||
|
{__(
|
||||||
|
"All content fees are charged in LBC. For non-LBC payment methods, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase."
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
|
<FormRow
|
||||||
|
label="License"
|
||||||
|
type="select"
|
||||||
|
value={this.state.licenseType}
|
||||||
|
ref={row => {
|
||||||
|
this._meta_license = row;
|
||||||
|
}}
|
||||||
|
onChange={event => {
|
||||||
|
this.handleLicenseTypeChange(event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option />
|
||||||
|
<option value="publicDomain">{__("Public Domain")}</option>
|
||||||
|
<option
|
||||||
|
value="cc-by"
|
||||||
|
data-url="https://creativecommons.org/licenses/by/4.0/legalcode"
|
||||||
|
>
|
||||||
|
{__("Creative Commons Attribution 4.0 International")}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="cc-by-sa"
|
||||||
|
data-url="https://creativecommons.org/licenses/by-sa/4.0/legalcode"
|
||||||
|
>
|
||||||
|
{__(
|
||||||
|
"Creative Commons Attribution-ShareAlike 4.0 International"
|
||||||
|
)}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="cc-by-nd"
|
||||||
|
data-url="https://creativecommons.org/licenses/by-nd/4.0/legalcode"
|
||||||
|
>
|
||||||
|
{__(
|
||||||
|
"Creative Commons Attribution-NoDerivatives 4.0 International"
|
||||||
|
)}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="cc-by-nc"
|
||||||
|
data-url="https://creativecommons.org/licenses/by-nc/4.0/legalcode"
|
||||||
|
>
|
||||||
|
{__(
|
||||||
|
"Creative Commons Attribution-NonCommercial 4.0 International"
|
||||||
|
)}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="cc-by-nc-sa"
|
||||||
|
data-url="https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode"
|
||||||
|
>
|
||||||
|
{__(
|
||||||
|
"Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International"
|
||||||
|
)}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="cc-by-nc-nd"
|
||||||
|
data-url="https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode"
|
||||||
|
>
|
||||||
|
{__(
|
||||||
|
"Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International"
|
||||||
|
)}
|
||||||
|
</option>
|
||||||
|
<option value="copyright">
|
||||||
|
{__("Copyrighted...")}
|
||||||
|
</option>
|
||||||
|
<option value="other">
|
||||||
|
{__("Other...")}
|
||||||
|
</option>
|
||||||
|
</FormRow>
|
||||||
|
|
||||||
|
{this.state.licenseType == "copyright"
|
||||||
|
? <FormRow
|
||||||
|
label={__("Copyright notice")}
|
||||||
|
type="text"
|
||||||
|
name="copyright-notice"
|
||||||
|
value={this.state.copyrightNotice}
|
||||||
|
onChange={event => {
|
||||||
|
this.handleCopyrightNoticeChange(event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{this.state.licenseType == "other"
|
||||||
|
? <FormRow
|
||||||
|
label={__("License description")}
|
||||||
|
type="text"
|
||||||
|
name="other-license-description"
|
||||||
|
value={this.state.otherLicenseDescription}
|
||||||
|
onChange={event => {
|
||||||
|
this.handleOtherLicenseDescriptionChange(event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{this.state.licenseType == "other"
|
||||||
|
? <FormRow
|
||||||
|
label={__("License URL")}
|
||||||
|
type="text"
|
||||||
|
name="other-license-url"
|
||||||
|
value={this.state.otherLicenseUrl}
|
||||||
|
onChange={event => {
|
||||||
|
this.handleOtherLicenseUrlChange(event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<ChannelSection
|
||||||
|
{...this.props}
|
||||||
|
handleChannelChange={this.handleChannelChange.bind(this)}
|
||||||
|
channel={this.state.channel}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<section className="card">
|
||||||
|
<div className="card__title-primary">
|
||||||
|
<h4>{__("Content URL")}</h4>
|
||||||
|
<div className="card__subtitle">
|
||||||
|
{__(
|
||||||
|
"This is the exact address where people find your content (ex. lbry://myvideo)."
|
||||||
|
)}
|
||||||
|
{" "}
|
||||||
|
<Link
|
||||||
|
label={__("Learn more")}
|
||||||
|
href="https://lbry.io/faq/naming"
|
||||||
|
/>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
<FormRow
|
||||||
|
prefix={`lbry://${this.state.channel === "anonymous"
|
||||||
|
? ""
|
||||||
|
: `${this.state.channel}/`}`}
|
||||||
|
type="text"
|
||||||
|
ref="name"
|
||||||
|
placeholder="myname"
|
||||||
|
value={this.state.rawName}
|
||||||
|
onChange={event => {
|
||||||
|
this.handleNameChange(event);
|
||||||
|
}}
|
||||||
|
helper={this.getNameBidHelpText()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{this.state.rawName
|
||||||
|
? <div className="card__content">
|
||||||
|
<FormRow
|
||||||
|
ref="bid"
|
||||||
|
type="number"
|
||||||
|
step="0.01"
|
||||||
|
label={__("Deposit")}
|
||||||
|
postfix="LBC"
|
||||||
|
onChange={event => {
|
||||||
|
this.handleBidChange(event);
|
||||||
|
}}
|
||||||
|
value={this.state.bid}
|
||||||
|
placeholder={this.claim() ? this.topClaimValue() + 10 : 100}
|
||||||
|
helper={lbcInputHelp}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
: ""}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="card">
|
||||||
|
<div className="card__title-primary">
|
||||||
|
<h4>{__("Terms of Service")}</h4>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
<FormRow
|
||||||
|
label={
|
||||||
|
<span>
|
||||||
|
{__("I agree to the")}
|
||||||
|
{" "}
|
||||||
|
<Link
|
||||||
|
href="https://www.lbry.io/termsofservice"
|
||||||
|
label={__("LBRY terms of service")}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
type="checkbox"
|
||||||
|
checked={this.state.tosAgree}
|
||||||
|
onChange={event => {
|
||||||
|
this.handleTOSChange(event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="card-series-submit">
|
||||||
|
<Link
|
||||||
|
button="primary"
|
||||||
|
label={
|
||||||
|
!this.state.submitting ? __("Publish") : __("Publishing...")
|
||||||
|
}
|
||||||
|
onClick={event => {
|
||||||
|
this.handleSubmit(event);
|
||||||
|
}}
|
||||||
|
disabled={
|
||||||
|
this.state.submitting ||
|
||||||
|
(this.state.uri &&
|
||||||
|
this.props.resolvingUris.indexOf(this.state.uri) !== -1) ||
|
||||||
|
(this.claim() &&
|
||||||
|
!this.topClaimIsMine() &&
|
||||||
|
this.state.bid <= this.topClaimValue())
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Link
|
||||||
|
button="cancel"
|
||||||
|
onClick={this.props.back}
|
||||||
|
label={__("Cancel")}
|
||||||
|
/>
|
||||||
|
<input type="submit" className="hidden" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
isOpen={this.state.modal == "publishStarted"}
|
||||||
|
contentLabel={__("File published")}
|
||||||
|
onConfirmed={event => {
|
||||||
|
this.handlePublishStartedConfirmed(event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
{__("Your file has been published to LBRY at the address")}
|
||||||
|
{" "}<code>{this.state.uri}</code>!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{__(
|
||||||
|
'The file will take a few minutes to appear for other LBRY users. Until then it will be listed as "pending" under your published files.'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
isOpen={this.state.modal == "error"}
|
||||||
|
contentLabel={__("Error publishing file")}
|
||||||
|
onConfirmed={event => {
|
||||||
|
this.closeModal(event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{__(
|
||||||
|
"The following error occurred when attempting to publish your file"
|
||||||
|
)}: {this.state.errorMessage}
|
||||||
|
</Modal>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PublishForm;
|
|
@ -1,7 +1,6 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import {
|
import {
|
||||||
makeSelectHasClaimedReward,
|
|
||||||
makeSelectClaimRewardError,
|
makeSelectClaimRewardError,
|
||||||
makeSelectRewardByType,
|
makeSelectRewardByType,
|
||||||
makeSelectIsRewardClaimPending,
|
makeSelectIsRewardClaimPending,
|
||||||
|
@ -11,13 +10,11 @@ import { doClaimReward, doClaimRewardClearError } from "actions/rewards";
|
||||||
import RewardLink from "./view";
|
import RewardLink from "./view";
|
||||||
|
|
||||||
const makeSelect = () => {
|
const makeSelect = () => {
|
||||||
const selectHasClaimedReward = makeSelectHasClaimedReward();
|
|
||||||
const selectIsPending = makeSelectIsRewardClaimPending();
|
const selectIsPending = makeSelectIsRewardClaimPending();
|
||||||
const selectReward = makeSelectRewardByType();
|
const selectReward = makeSelectRewardByType();
|
||||||
const selectError = makeSelectClaimRewardError();
|
const selectError = makeSelectClaimRewardError();
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
isClaimed: selectHasClaimedReward(state, props),
|
|
||||||
errorMessage: selectError(state, props),
|
errorMessage: selectError(state, props),
|
||||||
isPending: selectIsPending(state, props),
|
isPending: selectIsPending(state, props),
|
||||||
reward: selectReward(state, props),
|
reward: selectReward(state, props),
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Icon } from "component/common";
|
|
||||||
import Modal from "component/modal";
|
import Modal from "component/modal";
|
||||||
import Link from "component/link";
|
import Link from "component/link";
|
||||||
|
|
||||||
|
@ -10,22 +9,19 @@ const RewardLink = props => {
|
||||||
claimReward,
|
claimReward,
|
||||||
clearError,
|
clearError,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
isClaimed,
|
|
||||||
isPending,
|
isPending,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="reward-link">
|
<div className="reward-link">
|
||||||
{isClaimed
|
<Link
|
||||||
? <span><Icon icon="icon-check" /> Reward claimed.</span>
|
button={button ? button : "alt"}
|
||||||
: <Link
|
disabled={isPending}
|
||||||
button={button ? button : "alt"}
|
label={isPending ? __("Claiming...") : __("Claim Reward")}
|
||||||
disabled={isPending}
|
onClick={() => {
|
||||||
label={isPending ? __("Claiming...") : __("Claim Reward")}
|
claimReward(reward);
|
||||||
onClick={() => {
|
}}
|
||||||
claimReward(reward);
|
/>
|
||||||
}}
|
|
||||||
/>}
|
|
||||||
{errorMessage
|
{errorMessage
|
||||||
? <Modal
|
? <Modal
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
|
|
|
@ -13,6 +13,7 @@ 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";
|
||||||
|
import AuthPage from "page/auth";
|
||||||
|
|
||||||
const route = (page, routesMap) => {
|
const route = (page, routesMap) => {
|
||||||
const component = routesMap[page];
|
const component = routesMap[page];
|
||||||
|
@ -24,22 +25,23 @@ const Router = props => {
|
||||||
const { currentPage, params } = props;
|
const { currentPage, params } = props;
|
||||||
|
|
||||||
return route(currentPage, {
|
return route(currentPage, {
|
||||||
settings: <SettingsPage {...params} />,
|
auth: <AuthPage params={params} />,
|
||||||
help: <HelpPage {...params} />,
|
channel: <ChannelPage params={params} />,
|
||||||
report: <ReportPage {...params} />,
|
developer: <DeveloperPage params={params} />,
|
||||||
downloaded: <FileListDownloaded {...params} />,
|
discover: <DiscoverPage params={params} />,
|
||||||
published: <FileListPublished {...params} />,
|
downloaded: <FileListDownloaded params={params} />,
|
||||||
start: <StartPage {...params} />,
|
help: <HelpPage params={params} />,
|
||||||
wallet: <WalletPage {...params} />,
|
publish: <PublishPage params={params} />,
|
||||||
send: <WalletPage {...params} />,
|
published: <FileListPublished params={params} />,
|
||||||
receive: <WalletPage {...params} />,
|
receive: <WalletPage params={params} />,
|
||||||
show: <ShowPage {...params} />,
|
report: <ReportPage params={params} />,
|
||||||
channel: <ChannelPage {...params} />,
|
rewards: <RewardsPage params={params} />,
|
||||||
publish: <PublishPage {...params} />,
|
search: <SearchPage params={params} />,
|
||||||
developer: <DeveloperPage {...params} />,
|
send: <WalletPage params={params} />,
|
||||||
discover: <DiscoverPage {...params} />,
|
settings: <SettingsPage params={params} />,
|
||||||
rewards: <RewardsPage {...params} />,
|
show: <ShowPage params={params} />,
|
||||||
search: <SearchPage {...params} />,
|
start: <StartPage params={params} />,
|
||||||
|
wallet: <WalletPage params={params} />,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import lbry from "../lbry.js";
|
|
||||||
import LoadScreen from "./load_screen.js";
|
|
||||||
|
|
||||||
export class SplashScreen extends React.PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
message: React.PropTypes.string,
|
|
||||||
onLoadDone: React.PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
details: __("Starting daemon"),
|
|
||||||
message: __("Connecting"),
|
|
||||||
isLagging: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStatus() {
|
|
||||||
lbry.status().then(status => {
|
|
||||||
this._updateStatusCallback(status);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateStatusCallback(status) {
|
|
||||||
const startupStatus = status.startup_status;
|
|
||||||
if (startupStatus.code == "started") {
|
|
||||||
// Wait until we are able to resolve a name before declaring
|
|
||||||
// that we are done.
|
|
||||||
// TODO: This is a hack, and the logic should live in the daemon
|
|
||||||
// to give us a better sense of when we are actually started
|
|
||||||
this.setState({
|
|
||||||
message: __("Testing Network"),
|
|
||||||
details: __("Waiting for name resolution"),
|
|
||||||
isLagging: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
lbry.resolve({ uri: "lbry://one" }).then(() => {
|
|
||||||
this.props.onLoadDone();
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
details: startupStatus.message + (startupStatus.is_lagging ? "" : "..."),
|
|
||||||
isLagging: startupStatus.is_lagging,
|
|
||||||
});
|
|
||||||
setTimeout(() => {
|
|
||||||
this.updateStatus();
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
lbry
|
|
||||||
.connect()
|
|
||||||
.then(() => {
|
|
||||||
this.updateStatus();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.setState({
|
|
||||||
isLagging: true,
|
|
||||||
message: __("Connection Failure"),
|
|
||||||
details: __(
|
|
||||||
"Try closing all LBRY processes and starting again. If this still happens, your anti-virus software or firewall may be preventing LBRY from connecting. Contact hello@lbry.io if you think this is a software bug."
|
|
||||||
),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<LoadScreen
|
|
||||||
message={this.state.message}
|
|
||||||
details={this.state.details}
|
|
||||||
isWarning={this.state.isLagging}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SplashScreen;
|
|
19
ui/js/component/splash/index.js
Normal file
19
ui/js/component/splash/index.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
|
import { selectCurrentModal, selectDaemonVersionMatched } from "selectors/app";
|
||||||
|
import { doCheckDaemonVersion } from "actions/app";
|
||||||
|
import SplashScreen from "./view";
|
||||||
|
|
||||||
|
const select = state => {
|
||||||
|
return {
|
||||||
|
modal: selectCurrentModal(state),
|
||||||
|
daemonVersionMatched: selectDaemonVersionMatched(state),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
checkDaemonVersion: () => dispatch(doCheckDaemonVersion()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(SplashScreen);
|
121
ui/js/component/splash/view.jsx
Normal file
121
ui/js/component/splash/view.jsx
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import React from "react";
|
||||||
|
import lbry from "../../lbry.js";
|
||||||
|
import LoadScreen from "../load_screen.js";
|
||||||
|
import ModalIncompatibleDaemon from "../modalIncompatibleDaemon";
|
||||||
|
import ModalUpgrade from "component/modalUpgrade";
|
||||||
|
import ModalDownloading from "component/modalDownloading";
|
||||||
|
|
||||||
|
export class SplashScreen extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
message: React.PropTypes.string,
|
||||||
|
onLoadDone: React.PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
details: __("Starting daemon"),
|
||||||
|
message: __("Connecting"),
|
||||||
|
isRunning: false,
|
||||||
|
isLagging: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatus() {
|
||||||
|
lbry.status().then(status => {
|
||||||
|
this._updateStatusCallback(status);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateStatusCallback(status) {
|
||||||
|
const startupStatus = status.startup_status;
|
||||||
|
if (startupStatus.code == "started") {
|
||||||
|
// Wait until we are able to resolve a name before declaring
|
||||||
|
// that we are done.
|
||||||
|
// TODO: This is a hack, and the logic should live in the daemon
|
||||||
|
// to give us a better sense of when we are actually started
|
||||||
|
this.setState({
|
||||||
|
message: __("Testing Network"),
|
||||||
|
details: __("Waiting for name resolution"),
|
||||||
|
isLagging: false,
|
||||||
|
isRunning: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
lbry.resolve({ uri: "lbry://one" }).then(() => {
|
||||||
|
// Only leave the load screen if the daemon version matched;
|
||||||
|
// otherwise we'll notify the user at the end of the load screen.
|
||||||
|
|
||||||
|
if (this.props.daemonVersionMatched) {
|
||||||
|
this.props.onReadyToLaunch();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
status.blockchain_status &&
|
||||||
|
status.blockchain_status.blocks_behind > 0
|
||||||
|
) {
|
||||||
|
const format = status.blockchain_status.blocks_behind == 1
|
||||||
|
? "%s block behind"
|
||||||
|
: "%s blocks behind";
|
||||||
|
this.setState({
|
||||||
|
message: __("Blockchain Sync"),
|
||||||
|
details: __(format, status.blockchain_status.blocks_behind),
|
||||||
|
isLagging: startupStatus.is_lagging,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
message: __("Network Loading"),
|
||||||
|
details:
|
||||||
|
startupStatus.message + (startupStatus.is_lagging ? "" : "..."),
|
||||||
|
isLagging: startupStatus.is_lagging,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
this.updateStatus();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
lbry
|
||||||
|
.connect()
|
||||||
|
.then(this.props.checkDaemonVersion)
|
||||||
|
.then(() => {
|
||||||
|
this.updateStatus();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.setState({
|
||||||
|
isLagging: true,
|
||||||
|
message: __("Connection Failure"),
|
||||||
|
details: __(
|
||||||
|
"Try closing all LBRY processes and starting again. If this still happens, 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() {
|
||||||
|
const { modal } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<LoadScreen
|
||||||
|
message={this.state.message}
|
||||||
|
details={this.state.details}
|
||||||
|
isWarning={this.state.isLagging}
|
||||||
|
/>
|
||||||
|
{/* Temp hack: don't show any modals on splash screen daemon is running;
|
||||||
|
daemon doesn't let you quit during startup, so the "Quit" buttons
|
||||||
|
in the modals won't work. */}
|
||||||
|
{modal == "incompatibleDaemon" &&
|
||||||
|
this.state.isRunning &&
|
||||||
|
<ModalIncompatibleDaemon />}
|
||||||
|
{modal == "upgrade" && this.state.isRunning && <ModalUpgrade />}
|
||||||
|
{modal == "downloading" && this.state.isRunning && <ModalDownloading />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SplashScreen;
|
5
ui/js/component/truncatedMarkdown/index.js
Normal file
5
ui/js/component/truncatedMarkdown/index.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import TruncatedMarkdown from "./view";
|
||||||
|
|
||||||
|
export default connect()(TruncatedMarkdown);
|
39
ui/js/component/truncatedMarkdown/view.jsx
Normal file
39
ui/js/component/truncatedMarkdown/view.jsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import React from "react";
|
||||||
|
import ReactMarkdown from "react-markdown";
|
||||||
|
import ReactDOMServer from "react-dom/server";
|
||||||
|
|
||||||
|
class TruncatedMarkdown extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
lines: React.PropTypes.number,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
lines: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
transformMarkdown(text) {
|
||||||
|
// render markdown to html string then trim html tag
|
||||||
|
let htmlString = ReactDOMServer.renderToStaticMarkup(
|
||||||
|
<ReactMarkdown source={this.props.children} />
|
||||||
|
);
|
||||||
|
var txt = document.createElement("textarea");
|
||||||
|
txt.innerHTML = htmlString;
|
||||||
|
return txt.value.replace(/<(?:.|\n)*?>/gm, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let content = this.props.children && typeof this.props.children === "string"
|
||||||
|
? this.transformMarkdown(this.props.children)
|
||||||
|
: this.props.children;
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className="truncated-text"
|
||||||
|
style={{ WebkitLineClamp: this.props.lines }}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TruncatedMarkdown;
|
|
@ -27,7 +27,6 @@ class UserEmailNew extends React.PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="form-input-width"
|
|
||||||
onSubmit={event => {
|
onSubmit={event => {
|
||||||
this.handleSubmit(event);
|
this.handleSubmit(event);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -13,7 +13,7 @@ class UserEmailVerify extends React.PureComponent {
|
||||||
|
|
||||||
handleCodeChanged(event) {
|
handleCodeChanged(event) {
|
||||||
this.setState({
|
this.setState({
|
||||||
code: event.target.value,
|
code: String(event.target.value).trim(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,18 +24,16 @@ class UserEmailVerify extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { errorMessage, isPending } = this.props;
|
const { errorMessage, isPending } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="form-input-width"
|
|
||||||
onSubmit={event => {
|
onSubmit={event => {
|
||||||
this.handleSubmit(event);
|
this.handleSubmit(event);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<p>{__("Please enter the verification code emailed to you.")}</p>
|
||||||
<FormRow
|
<FormRow
|
||||||
type="text"
|
type="text"
|
||||||
label={__("Verification Code")}
|
label={__("Verification Code")}
|
||||||
placeholder="a94bXXXXXXXXXXXXXX"
|
|
||||||
name="code"
|
name="code"
|
||||||
value={this.state.code}
|
value={this.state.code}
|
||||||
onChange={event => {
|
onChange={event => {
|
||||||
|
@ -48,7 +46,7 @@ class UserEmailVerify extends React.PureComponent {
|
||||||
<p>
|
<p>
|
||||||
{__("Email")}{" "}
|
{__("Email")}{" "}
|
||||||
<Link href="mailto:help@lbry.io" label="help@lbry.io" />{" "}
|
<Link href="mailto:help@lbry.io" label="help@lbry.io" />{" "}
|
||||||
{__("if you did not receive or are having trouble with your code.")}
|
{__("if you encounter any trouble with your code.")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-row-submit form-row-submit--with-footer">
|
<div className="form-row-submit form-row-submit--with-footer">
|
||||||
|
|
28
ui/js/component/userVerify/index.js
Normal file
28
ui/js/component/userVerify/index.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { doNavigate } from "actions/app";
|
||||||
|
import { doUserIdentityVerify } from "actions/user";
|
||||||
|
import rewards from "rewards";
|
||||||
|
import { makeSelectRewardByType } from "selectors/rewards";
|
||||||
|
import {
|
||||||
|
selectIdentityVerifyIsPending,
|
||||||
|
selectIdentityVerifyErrorMessage,
|
||||||
|
} from "selectors/user";
|
||||||
|
import UserVerify from "./view";
|
||||||
|
|
||||||
|
const select = (state, props) => {
|
||||||
|
const selectReward = makeSelectRewardByType();
|
||||||
|
|
||||||
|
return {
|
||||||
|
isPending: selectIdentityVerifyIsPending(state),
|
||||||
|
errorMessage: selectIdentityVerifyErrorMessage(state),
|
||||||
|
reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
navigate: uri => dispatch(doNavigate(uri)),
|
||||||
|
verifyUserIdentity: token => dispatch(doUserIdentityVerify(token)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(UserVerify);
|
65
ui/js/component/userVerify/view.jsx
Normal file
65
ui/js/component/userVerify/view.jsx
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import React from "react";
|
||||||
|
import { CreditAmount } from "component/common";
|
||||||
|
import Link from "component/link";
|
||||||
|
import CardVerify from "component/cardVerify";
|
||||||
|
|
||||||
|
class UserVerify extends React.PureComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
code: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCodeChanged(event) {
|
||||||
|
this.setState({
|
||||||
|
code: event.target.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onToken(data) {
|
||||||
|
this.props.verifyUserIdentity(data.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { errorMessage, isPending, navigate } = this.props;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{__(
|
||||||
|
"To ensure you are a real person, we require a valid credit or debit card."
|
||||||
|
) +
|
||||||
|
" " +
|
||||||
|
__("There is no charge at all, now or in the future.") +
|
||||||
|
" "}
|
||||||
|
<Link
|
||||||
|
href="https://lbry.io/faq/identity-requirements"
|
||||||
|
label={__("Read More")}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
{errorMessage && <p className="form-field__error">{errorMessage}</p>}
|
||||||
|
<p>
|
||||||
|
<CardVerify
|
||||||
|
label={__("Link Card and Finish")}
|
||||||
|
disabled={isPending}
|
||||||
|
token={this.onToken.bind(this)}
|
||||||
|
stripeKey="pk_live_e8M4dRNnCCbmpZzduEUZBgJO"
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{__(
|
||||||
|
"You can continue without this step, but you will not be eligible to earn rewards."
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
onClick={() => navigate("/discover")}
|
||||||
|
button="alt"
|
||||||
|
label={__("Skip Rewards")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserVerify;
|
|
@ -4,12 +4,31 @@ import Link from "component/link";
|
||||||
import Modal from "component/modal";
|
import Modal from "component/modal";
|
||||||
|
|
||||||
class VideoPlayButton extends React.PureComponent {
|
class VideoPlayButton extends React.PureComponent {
|
||||||
|
componentDidMount() {
|
||||||
|
this.keyDownListener = this.onKeyDown.bind(this);
|
||||||
|
document.addEventListener("keydown", this.keyDownListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener("keydown", this.keyDownListener);
|
||||||
|
}
|
||||||
|
|
||||||
onPurchaseConfirmed() {
|
onPurchaseConfirmed() {
|
||||||
this.props.closeModal();
|
this.props.closeModal();
|
||||||
this.props.startPlaying();
|
this.props.startPlaying();
|
||||||
this.props.loadVideo(this.props.uri);
|
this.props.loadVideo(this.props.uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onKeyDown(event) {
|
||||||
|
if (
|
||||||
|
"input" !== event.target.tagName.toLowerCase() &&
|
||||||
|
"Space" === event.code
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.onWatchClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onWatchClick() {
|
onWatchClick() {
|
||||||
this.props.purchaseUri(this.props.uri).then(() => {
|
this.props.purchaseUri(this.props.uri).then(() => {
|
||||||
if (!this.props.modal) {
|
if (!this.props.modal) {
|
||||||
|
@ -28,7 +47,6 @@ class VideoPlayButton extends React.PureComponent {
|
||||||
modal,
|
modal,
|
||||||
closeModal,
|
closeModal,
|
||||||
isLoading,
|
isLoading,
|
||||||
costInfo,
|
|
||||||
fileInfo,
|
fileInfo,
|
||||||
mediaType,
|
mediaType,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -41,10 +59,7 @@ class VideoPlayButton extends React.PureComponent {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const disabled =
|
const disabled = isLoading || fileInfo === undefined;
|
||||||
isLoading ||
|
|
||||||
fileInfo === undefined ||
|
|
||||||
(fileInfo === null && (!costInfo || costInfo.cost === undefined));
|
|
||||||
const icon = ["audio", "video"].indexOf(mediaType) !== -1
|
const icon = ["audio", "video"].indexOf(mediaType) !== -1
|
||||||
? "icon-play"
|
? "icon-play"
|
||||||
: "icon-folder-o";
|
: "icon-folder-o";
|
||||||
|
@ -59,13 +74,6 @@ class VideoPlayButton extends React.PureComponent {
|
||||||
icon={icon}
|
icon={icon}
|
||||||
onClick={this.onWatchClick.bind(this)}
|
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
|
<Modal
|
||||||
type="confirm"
|
type="confirm"
|
||||||
isOpen={modal == "affirmPurchaseAndPlay"}
|
isOpen={modal == "affirmPurchaseAndPlay"}
|
||||||
|
@ -73,9 +81,11 @@ class VideoPlayButton extends React.PureComponent {
|
||||||
onConfirmed={this.onPurchaseConfirmed.bind(this)}
|
onConfirmed={this.onPurchaseConfirmed.bind(this)}
|
||||||
onAborted={closeModal}
|
onAborted={closeModal}
|
||||||
>
|
>
|
||||||
{__("This will purchase")} <strong>{title}</strong> {__("for")}
|
{__("This will purchase")} <strong>{title}</strong> {__("for")}{" "}
|
||||||
{" "}<strong><FilePrice uri={uri} look="plain" /></strong>
|
<strong>
|
||||||
{" "}{__("credits")}.
|
<FilePrice uri={uri} look="plain" />
|
||||||
|
</strong>{" "}
|
||||||
|
{__("credits")}.
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={modal == "timedOut"}
|
isOpen={modal == "timedOut"}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import { setSession, getSession } from "utils";
|
||||||
import LoadingScreen from "./loading-screen";
|
import LoadingScreen from "./loading-screen";
|
||||||
|
|
||||||
class VideoPlayer extends React.PureComponent {
|
class VideoPlayer extends React.PureComponent {
|
||||||
|
static MP3_CONTENT_TYPES = ["audio/mpeg3", "audio/mpeg"];
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
@ -15,11 +17,13 @@ class VideoPlayer extends React.PureComponent {
|
||||||
startedPlaying: false,
|
startedPlaying: false,
|
||||||
unplayable: false,
|
unplayable: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.togglePlayListener = this.togglePlay.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const container = this.refs.media;
|
const container = this.refs.media;
|
||||||
const { mediaType } = this.props;
|
const { contentType, downloadPath, mediaType } = this.props;
|
||||||
const loadedMetadata = e => {
|
const loadedMetadata = e => {
|
||||||
this.setState({ hasMetadata: true, startedPlaying: true });
|
this.setState({ hasMetadata: true, startedPlaying: true });
|
||||||
this.refs.media.children[0].play();
|
this.refs.media.children[0].play();
|
||||||
|
@ -37,15 +41,22 @@ class VideoPlayer extends React.PureComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
player.append(
|
// use renderAudio override for mp3
|
||||||
this.file(),
|
if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) {
|
||||||
container,
|
this.renderAudio(container, null, false);
|
||||||
{ autoplay: false, controls: true },
|
} else {
|
||||||
renderMediaCallback.bind(this)
|
player.append(
|
||||||
);
|
this.file(),
|
||||||
|
container,
|
||||||
|
{ autoplay: false, controls: true },
|
||||||
|
renderMediaCallback.bind(this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("keydown", this.togglePlayListener);
|
||||||
const mediaElement = this.refs.media.children[0];
|
const mediaElement = this.refs.media.children[0];
|
||||||
if (mediaElement) {
|
if (mediaElement) {
|
||||||
|
mediaElement.addEventListener("click", this.togglePlayListener);
|
||||||
mediaElement.addEventListener(
|
mediaElement.addEventListener(
|
||||||
"loadedmetadata",
|
"loadedmetadata",
|
||||||
loadedMetadata.bind(this),
|
loadedMetadata.bind(this),
|
||||||
|
@ -53,7 +64,6 @@ class VideoPlayer extends React.PureComponent {
|
||||||
once: true,
|
once: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
mediaElement.addEventListener(
|
mediaElement.addEventListener(
|
||||||
"webkitfullscreenchange",
|
"webkitfullscreenchange",
|
||||||
win32FullScreenChange.bind(this)
|
win32FullScreenChange.bind(this)
|
||||||
|
@ -65,19 +75,67 @@ class VideoPlayer extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener("keydown", this.togglePlayListener);
|
||||||
|
const mediaElement = this.refs.media.children[0];
|
||||||
|
if (mediaElement) {
|
||||||
|
mediaElement.removeEventListener("click", this.togglePlayListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAudio(container, autoplay) {
|
||||||
|
if (container.firstChild) {
|
||||||
|
container.firstChild.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear the container
|
||||||
|
const { downloadPath } = this.props;
|
||||||
|
const audio = document.createElement("audio");
|
||||||
|
audio.autoplay = autoplay;
|
||||||
|
audio.controls = true;
|
||||||
|
audio.src = downloadPath;
|
||||||
|
container.appendChild(audio);
|
||||||
|
}
|
||||||
|
|
||||||
|
togglePlay(event) {
|
||||||
|
// ignore all events except click and spacebar keydown, or input events in a form control
|
||||||
|
if (
|
||||||
|
"keydown" === event.type &&
|
||||||
|
("Space" !== event.code || "input" === event.target.tagName.toLowerCase())
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
const mediaElement = this.refs.media.children[0];
|
||||||
|
if (mediaElement) {
|
||||||
|
if (!mediaElement.paused) {
|
||||||
|
mediaElement.pause();
|
||||||
|
} else {
|
||||||
|
mediaElement.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getPreferredVolume() {
|
getPreferredVolume() {
|
||||||
const volumePreference = parseFloat(getSession("prefs_volume"));
|
const volumePreference = parseFloat(getSession("prefs_volume"));
|
||||||
return isNaN(volumePreference) ? 1 : volumePreference;
|
return isNaN(volumePreference) ? 1 : volumePreference;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
const { mediaType, downloadCompleted } = this.props;
|
const { contentType, downloadCompleted } = this.props;
|
||||||
const { startedPlaying } = this.state;
|
const { startedPlaying } = this.state;
|
||||||
|
|
||||||
if (this.playableType() && !startedPlaying && downloadCompleted) {
|
if (this.playableType() && !startedPlaying && downloadCompleted) {
|
||||||
const container = this.refs.media.children[0];
|
const container = this.refs.media.children[0];
|
||||||
|
|
||||||
player.render(this.file(), container, { autoplay: true, controls: true });
|
if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) {
|
||||||
|
this.renderAudio(this.refs.media, true);
|
||||||
|
} else {
|
||||||
|
player.render(this.file(), container, {
|
||||||
|
autoplay: true,
|
||||||
|
controls: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,25 @@ class Video extends React.PureComponent {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
// reset playing state upon change path action
|
||||||
|
if (
|
||||||
|
!this.isMediaSame(nextProps) &&
|
||||||
|
this.props.fileInfo &&
|
||||||
|
this.state.isPlaying
|
||||||
|
) {
|
||||||
|
this.state.isPlaying = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isMediaSame(nextProps) {
|
||||||
|
return (
|
||||||
|
this.props.fileInfo &&
|
||||||
|
nextProps.fileInfo &&
|
||||||
|
this.props.fileInfo.outpoint === nextProps.fileInfo.outpoint
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
startPlaying() {
|
startPlaying() {
|
||||||
this.setState({
|
this.setState({
|
||||||
isPlaying: true,
|
isPlaying: true,
|
||||||
|
@ -98,6 +117,7 @@ class Video extends React.PureComponent {
|
||||||
poster={poster}
|
poster={poster}
|
||||||
downloadPath={fileInfo.download_path}
|
downloadPath={fileInfo.download_path}
|
||||||
mediaType={mediaType}
|
mediaType={mediaType}
|
||||||
|
contentType={contentType}
|
||||||
downloadCompleted={fileInfo.completed}
|
downloadCompleted={fileInfo.completed}
|
||||||
/>)}
|
/>)}
|
||||||
{!isPlaying &&
|
{!isPlaying &&
|
||||||
|
|
|
@ -12,8 +12,10 @@ const select = state => ({
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
onSearch: query => dispatch(doNavigate("/search", { query })),
|
onSearch: query => dispatch(doNavigate("/search", { query })),
|
||||||
onSubmit: query =>
|
onSubmit: (query, extraParams) =>
|
||||||
dispatch(doNavigate("/show", { uri: lbryuri.normalize(query) })),
|
dispatch(
|
||||||
|
doNavigate("/show", { uri: lbryuri.normalize(query), ...extraParams })
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(Wunderbar);
|
export default connect(select, perform)(Wunderbar);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
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";
|
||||||
|
import { parseQueryParams } from "util/query_params";
|
||||||
|
|
||||||
class WunderBar extends React.PureComponent {
|
class WunderBar extends React.PureComponent {
|
||||||
static TYPING_TIMEOUT = 800;
|
static TYPING_TIMEOUT = 800;
|
||||||
|
@ -120,12 +121,15 @@ 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",
|
||||||
|
extraParams = {};
|
||||||
|
|
||||||
this._resetOnNextBlur = false;
|
this._resetOnNextBlur = false;
|
||||||
clearTimeout(this._userTypingTimer);
|
clearTimeout(this._userTypingTimer);
|
||||||
|
|
||||||
const value = this._input.value.trim();
|
const parts = this._input.value.trim().split("?");
|
||||||
|
const value = parts.shift();
|
||||||
|
if (parts.length > 0) extraParams = parseQueryParams(parts.join(""));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
uri = lbryuri.normalize(value);
|
uri = lbryuri.normalize(value);
|
||||||
|
@ -136,7 +140,7 @@ class WunderBar extends React.PureComponent {
|
||||||
method = "onSearch";
|
method = "onSearch";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props[method](uri);
|
this.props[method](uri, extraParams);
|
||||||
this._input.blur();
|
this._input.blur();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,10 @@ 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 WINDOW_FOCUSED = "WINDOW_FOCUSED";
|
export const WINDOW_FOCUSED = "WINDOW_FOCUSED";
|
||||||
|
export const CHANGE_AFTER_AUTH_PATH = "CHANGE_AFTER_AUTH_PATH";
|
||||||
export const DAEMON_READY = "DAEMON_READY";
|
export const DAEMON_READY = "DAEMON_READY";
|
||||||
|
export const DAEMON_VERSION_MATCH = "DAEMON_VERSION_MATCH";
|
||||||
|
export const DAEMON_VERSION_MISMATCH = "DAEMON_VERSION_MISMATCH";
|
||||||
|
|
||||||
// Upgrades
|
// Upgrades
|
||||||
export const UPGRADE_CANCELLED = "UPGRADE_CANCELLED";
|
export const UPGRADE_CANCELLED = "UPGRADE_CANCELLED";
|
||||||
|
@ -47,7 +49,7 @@ export const FETCH_CLAIM_LIST_MINE_STARTED = "FETCH_CLAIM_LIST_MINE_STARTED";
|
||||||
export const FETCH_CLAIM_LIST_MINE_COMPLETED =
|
export const FETCH_CLAIM_LIST_MINE_COMPLETED =
|
||||||
"FETCH_CLAIM_LIST_MINE_COMPLETED";
|
"FETCH_CLAIM_LIST_MINE_COMPLETED";
|
||||||
export const FILE_LIST_STARTED = "FILE_LIST_STARTED";
|
export const FILE_LIST_STARTED = "FILE_LIST_STARTED";
|
||||||
export const FILE_LIST_COMPLETED = "FILE_LIST_COMPLETED";
|
export const FILE_LIST_SUCCEEDED = "FILE_LIST_SUCCEEDED";
|
||||||
export const FETCH_FILE_INFO_STARTED = "FETCH_FILE_INFO_STARTED";
|
export const FETCH_FILE_INFO_STARTED = "FETCH_FILE_INFO_STARTED";
|
||||||
export const FETCH_FILE_INFO_COMPLETED = "FETCH_FILE_INFO_COMPLETED";
|
export const FETCH_FILE_INFO_COMPLETED = "FETCH_FILE_INFO_COMPLETED";
|
||||||
export const FETCH_COST_INFO_STARTED = "FETCH_COST_INFO_STARTED";
|
export const FETCH_COST_INFO_STARTED = "FETCH_COST_INFO_STARTED";
|
||||||
|
@ -63,7 +65,16 @@ export const FETCH_AVAILABILITY_STARTED = "FETCH_AVAILABILITY_STARTED";
|
||||||
export const FETCH_AVAILABILITY_COMPLETED = "FETCH_AVAILABILITY_COMPLETED";
|
export const FETCH_AVAILABILITY_COMPLETED = "FETCH_AVAILABILITY_COMPLETED";
|
||||||
export const FILE_DELETE = "FILE_DELETE";
|
export const FILE_DELETE = "FILE_DELETE";
|
||||||
export const ABANDON_CLAIM_STARTED = "ABANDON_CLAIM_STARTED";
|
export const ABANDON_CLAIM_STARTED = "ABANDON_CLAIM_STARTED";
|
||||||
export const ABANDON_CLAIM_COMPLETED = "ABANDON_CLAIM_COMPLETED";
|
export const ABANDON_CLAIM_SUCCEEDED = "ABANDON_CLAIM_SUCCEEDED";
|
||||||
|
export const FETCH_CHANNEL_LIST_MINE_STARTED =
|
||||||
|
"FETCH_CHANNEL_LIST_MINE_STARTED";
|
||||||
|
export const FETCH_CHANNEL_LIST_MINE_COMPLETED =
|
||||||
|
"FETCH_CHANNEL_LIST_MINE_COMPLETED";
|
||||||
|
export const CREATE_CHANNEL_STARTED = "CREATE_CHANNEL_STARTED";
|
||||||
|
export const CREATE_CHANNEL_COMPLETED = "CREATE_CHANNEL_COMPLETED";
|
||||||
|
export const PUBLISH_STARTED = "PUBLISH_STARTED";
|
||||||
|
export const PUBLISH_COMPLETED = "PUBLISH_COMPLETED";
|
||||||
|
export const PUBLISH_FAILED = "PUBLISH_FAILED";
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
export const SEARCH_STARTED = "SEARCH_STARTED";
|
export const SEARCH_STARTED = "SEARCH_STARTED";
|
||||||
|
@ -86,9 +97,13 @@ 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_STARTED = "USER_EMAIL_VERIFY_STARTED";
|
||||||
export const USER_EMAIL_VERIFY_SUCCESS = "USER_EMAIL_VERIFY_SUCCESS";
|
export const USER_EMAIL_VERIFY_SUCCESS = "USER_EMAIL_VERIFY_SUCCESS";
|
||||||
export const USER_EMAIL_VERIFY_FAILURE = "USER_EMAIL_VERIFY_FAILURE";
|
export const USER_EMAIL_VERIFY_FAILURE = "USER_EMAIL_VERIFY_FAILURE";
|
||||||
|
export const USER_IDENTITY_VERIFY_STARTED = "USER_IDENTITY_VERIFY_STARTED";
|
||||||
|
export const USER_IDENTITY_VERIFY_SUCCESS = "USER_IDENTITY_VERIFY_SUCCESS";
|
||||||
|
export const USER_IDENTITY_VERIFY_FAILURE = "USER_IDENTITY_VERIFY_FAILURE";
|
||||||
export const USER_FETCH_STARTED = "USER_FETCH_STARTED";
|
export const USER_FETCH_STARTED = "USER_FETCH_STARTED";
|
||||||
export const USER_FETCH_SUCCESS = "USER_FETCH_SUCCESS";
|
export const USER_FETCH_SUCCESS = "USER_FETCH_SUCCESS";
|
||||||
export const USER_FETCH_FAILURE = "USER_FETCH_FAILURE";
|
export const USER_FETCH_FAILURE = "USER_FETCH_FAILURE";
|
||||||
|
export const FETCH_ACCESS_TOKEN_SUCCESS = "FETCH_ACCESS_TOKEN_SUCCESS";
|
||||||
|
|
||||||
// Rewards
|
// Rewards
|
||||||
export const FETCH_REWARDS_STARTED = "FETCH_REWARDS_STARTED";
|
export const FETCH_REWARDS_STARTED = "FETCH_REWARDS_STARTED";
|
||||||
|
@ -97,6 +112,14 @@ export const CLAIM_REWARD_STARTED = "CLAIM_REWARD_STARTED";
|
||||||
export const CLAIM_REWARD_SUCCESS = "CLAIM_REWARD_SUCCESS";
|
export const CLAIM_REWARD_SUCCESS = "CLAIM_REWARD_SUCCESS";
|
||||||
export const CLAIM_REWARD_FAILURE = "CLAIM_REWARD_FAILURE";
|
export const CLAIM_REWARD_FAILURE = "CLAIM_REWARD_FAILURE";
|
||||||
export const CLAIM_REWARD_CLEAR_ERROR = "CLAIM_REWARD_CLEAR_ERROR";
|
export const CLAIM_REWARD_CLEAR_ERROR = "CLAIM_REWARD_CLEAR_ERROR";
|
||||||
|
export const FETCH_REWARD_CONTENT_COMPLETED = "FETCH_REWARD_CONTENT_COMPLETED";
|
||||||
|
|
||||||
//Language
|
//Language
|
||||||
|
export const CHANGE_LANGUAGE = "CHANGE_LANGUAGE";
|
||||||
export const LANGUAGE_CHANGED = "LANGUAGE_CHANGED";
|
export const LANGUAGE_CHANGED = "LANGUAGE_CHANGED";
|
||||||
|
export const LANGUAGE_RESOLVED = "LANGUAGE_RESOLVED";
|
||||||
|
export const DOWNLOAD_LANGUAGE_STARTED = "DOWNLOAD_LANGUAGE_STARTED";
|
||||||
|
export const DOWNLOAD_LANGUAGE_SUCCEEDED = "DOWNLOAD_LANGUAGE_SUCCEEDED";
|
||||||
|
export const DOWNLOAD_LANGUAGE_FAILED = "DOWNLOAD_LANGUAGE_FAILED";
|
||||||
|
export const DOWNLOAD_LANGUAGES_STARTED = "DOWNLOAD_LANGUAGES_STARTED";
|
||||||
|
export const DOWNLOAD_LANGUAGES_COMPLETED = "DOWNLOAD_LANGUAGES_COMPLETED";
|
||||||
|
|
|
@ -1,2 +1,9 @@
|
||||||
export const WELCOME = "welcome";
|
|
||||||
export const CONFIRM_FILE_REMOVE = "confirmFileRemove";
|
export const CONFIRM_FILE_REMOVE = "confirmFileRemove";
|
||||||
|
export const INCOMPATIBLE_DAEMON = "incompatibleDaemon";
|
||||||
|
export const DOWNLOADING = "downloading";
|
||||||
|
export const ERROR = "error";
|
||||||
|
export const INSUFFICIENT_CREDITS = "insufficient_credits";
|
||||||
|
export const UPGRADE = "upgrade";
|
||||||
|
export const WELCOME = "welcome";
|
||||||
|
export const FIRST_REWARD = "first_reward";
|
||||||
|
export const AUTHENTICATION_FAILURE = "auth_failure";
|
||||||
|
|
|
@ -27,18 +27,19 @@ jsonrpc.call = function(
|
||||||
xhr.addEventListener("load", function() {
|
xhr.addEventListener("load", function() {
|
||||||
var response = JSON.parse(xhr.responseText);
|
var response = JSON.parse(xhr.responseText);
|
||||||
|
|
||||||
if (response.error) {
|
let error = response.error || (response.result && response.result.error);
|
||||||
|
if (error) {
|
||||||
if (errorCallback) {
|
if (errorCallback) {
|
||||||
errorCallback(response.error);
|
errorCallback(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: error.code,
|
||||||
message: response.error.message,
|
message: error.message || error,
|
||||||
data: response.error.data,
|
data: error.data,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
document.dispatchEvent(errorEvent);
|
document.dispatchEvent(errorEvent);
|
||||||
|
|
|
@ -223,55 +223,47 @@ lbry.publishDeprecated = function(
|
||||||
) {
|
) {
|
||||||
lbry.publish(params).then(
|
lbry.publish(params).then(
|
||||||
result => {
|
result => {
|
||||||
if (returnedPending) {
|
if (returnPendingTimeout) clearTimeout(returnPendingTimeout);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(returnPendingTimeout);
|
|
||||||
publishedCallback(result);
|
publishedCallback(result);
|
||||||
},
|
},
|
||||||
err => {
|
err => {
|
||||||
if (returnedPending) {
|
if (returnPendingTimeout) clearTimeout(returnPendingTimeout);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(returnPendingTimeout);
|
|
||||||
errorCallback(err);
|
errorCallback(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
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;
|
() => {
|
||||||
|
if (publishedCallback) {
|
||||||
|
savePendingPublish({
|
||||||
|
name: params.name,
|
||||||
|
channel_name: params.channel_name,
|
||||||
|
});
|
||||||
|
publishedCallback(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (publishedCallback) {
|
if (fileListedCallback) {
|
||||||
savePendingPublish({
|
const { name, channel_name } = params;
|
||||||
name: params.name,
|
savePendingPublish({
|
||||||
channel_name: params.channel_name,
|
name: params.name,
|
||||||
});
|
channel_name: params.channel_name,
|
||||||
publishedCallback(true);
|
});
|
||||||
}
|
fileListedCallback(true);
|
||||||
|
}
|
||||||
if (fileListedCallback) {
|
},
|
||||||
const { name, channel_name } = params;
|
2000,
|
||||||
savePendingPublish({
|
{ once: true }
|
||||||
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] =
|
outSettings[setting] = localStorageVal === null
|
||||||
localStorageVal === null
|
? lbry.defaultClientSettings[setting]
|
||||||
? lbry.defaultClientSettings[setting]
|
: JSON.parse(localStorageVal);
|
||||||
: JSON.parse(localStorageVal);
|
|
||||||
}
|
}
|
||||||
return outSettings;
|
return outSettings;
|
||||||
};
|
};
|
||||||
|
@ -296,15 +288,10 @@ lbry.setClientSetting = function(setting, value) {
|
||||||
return localStorage.setItem("setting_" + setting, JSON.stringify(value));
|
return localStorage.setItem("setting_" + setting, JSON.stringify(value));
|
||||||
};
|
};
|
||||||
|
|
||||||
//utilities
|
|
||||||
lbry.formatCredits = function(amount, precision) {
|
|
||||||
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(lbryuri.REGEXP_INVALID_URI, "");
|
||||||
return name;
|
return name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -429,10 +416,16 @@ lbry.file_list = function(params = {}) {
|
||||||
fileInfos => {
|
fileInfos => {
|
||||||
removePendingPublishIfNeeded({ name, channel_name, outpoint });
|
removePendingPublishIfNeeded({ name, channel_name, outpoint });
|
||||||
|
|
||||||
const dummyFileInfos = lbry
|
//if a naked file_list call, append the pending file infos
|
||||||
.getPendingPublishes()
|
if (!name && !channel_name && !outpoint) {
|
||||||
.map(pendingPublishToDummyFileInfo);
|
const dummyFileInfos = lbry
|
||||||
resolve([...fileInfos, ...dummyFileInfos]);
|
.getPendingPublishes()
|
||||||
|
.map(pendingPublishToDummyFileInfo);
|
||||||
|
|
||||||
|
resolve([...fileInfos, ...dummyFileInfos]);
|
||||||
|
} else {
|
||||||
|
resolve(fileInfos);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
reject
|
reject
|
||||||
);
|
);
|
||||||
|
|
|
@ -113,16 +113,24 @@ lbryio.call = function(resource, action, params = {}, method = "get") {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
lbryio._authToken = null;
|
||||||
|
|
||||||
lbryio.getAuthToken = () => {
|
lbryio.getAuthToken = () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
ipcRenderer.once("auth-token-response", (event, token) => {
|
if (lbryio._authToken) {
|
||||||
return resolve(token);
|
resolve(lbryio._authToken);
|
||||||
});
|
} else {
|
||||||
ipcRenderer.send("get-auth-token");
|
ipcRenderer.once("auth-token-response", (event, token) => {
|
||||||
|
lbryio._authToken = token;
|
||||||
|
return resolve(token);
|
||||||
|
});
|
||||||
|
ipcRenderer.send("get-auth-token");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
lbryio.setAuthToken = token => {
|
lbryio.setAuthToken = token => {
|
||||||
|
lbryio._authToken = token ? token.toString().trim() : null;
|
||||||
ipcRenderer.send("set-auth-token", token);
|
ipcRenderer.send("set-auth-token", token);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -136,8 +144,9 @@ lbryio.authenticate = function() {
|
||||||
resolve({
|
resolve({
|
||||||
id: 1,
|
id: 1,
|
||||||
language: "en",
|
language: "en",
|
||||||
has_email: true,
|
primary_email: "disabled@lbry.io",
|
||||||
has_verified_email: true,
|
has_verified_email: true,
|
||||||
|
is_identity_verified: true,
|
||||||
is_reward_approved: false,
|
is_reward_approved: false,
|
||||||
is_reward_eligible: false,
|
is_reward_eligible: false,
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,6 +3,8 @@ const CLAIM_ID_MAX_LEN = 40;
|
||||||
|
|
||||||
const lbryuri = {};
|
const lbryuri = {};
|
||||||
|
|
||||||
|
lbryuri.REGEXP_INVALID_URI = /[^A-Za-z0-9-]/g;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a LBRY name into its component parts. Throws errors with user-friendly
|
* Parses a LBRY name into its component parts. Throws errors with user-friendly
|
||||||
* messages for invalid names.
|
* messages for invalid names.
|
||||||
|
@ -70,7 +72,7 @@ lbryuri.parse = function(uri, requireProto = false) {
|
||||||
contentName = path;
|
contentName = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameBadChars = (channelName || name).match(/[^A-Za-z0-9-]/g);
|
const nameBadChars = (channelName || name).match(lbryuri.REGEXP_INVALID_URI);
|
||||||
if (nameBadChars) {
|
if (nameBadChars) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
__(
|
__(
|
||||||
|
@ -119,7 +121,7 @@ lbryuri.parse = function(uri, requireProto = false) {
|
||||||
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(lbryuri.REGEXP_INVALID_URI);
|
||||||
if (pathBadChars) {
|
if (pathBadChars) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
__(`Invalid character in path: %s`, pathBadChars.join(", "))
|
__(`Invalid character in path: %s`, pathBadChars.join(", "))
|
||||||
|
@ -203,6 +205,8 @@ lbryuri.build = function(uriObj, includeProto = true, allowExtraProps = false) {
|
||||||
/* 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) {
|
||||||
|
if (uri.match(/pending_claim/)) return uri;
|
||||||
|
|
||||||
const { name, path, bidPosition, claimSequence, claimId } = lbryuri.parse(
|
const { name, path, bidPosition, claimSequence, claimId } = lbryuri.parse(
|
||||||
uri
|
uri
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,11 +5,10 @@ import App from "component/app/index.js";
|
||||||
import SnackBar from "component/snackBar";
|
import SnackBar from "component/snackBar";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
import store from "store.js";
|
import store from "store.js";
|
||||||
import SplashScreen from "component/splash.js";
|
import SplashScreen from "component/splash";
|
||||||
import AuthOverlay from "component/authOverlay";
|
|
||||||
import { doChangePath, doNavigate, doDaemonReady } from "actions/app";
|
import { doChangePath, doNavigate, doDaemonReady } from "actions/app";
|
||||||
|
import { doDownloadLanguages } from "actions/settings";
|
||||||
import { toQueryString } from "util/query_params";
|
import { toQueryString } from "util/query_params";
|
||||||
import { selectBadgeNumber } from "selectors/app";
|
|
||||||
import * as types from "constants/action_types";
|
import * as types from "constants/action_types";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import http from "http";
|
import http from "http";
|
||||||
|
@ -39,11 +38,11 @@ window.addEventListener("popstate", (event, param) => {
|
||||||
let action;
|
let action;
|
||||||
|
|
||||||
if (hash !== "") {
|
if (hash !== "") {
|
||||||
const url = hash.split("#")[1];
|
const url = hash.replace(/^#/, "");
|
||||||
const params = event.state;
|
const { params, scrollY } = event.state || {};
|
||||||
const queryString = toQueryString(params);
|
const queryString = toQueryString(params);
|
||||||
|
|
||||||
app.store.dispatch(doChangePath(`${url}?${queryString}`));
|
app.store.dispatch(doChangePath(`${url}?${queryString}`, { scrollY }));
|
||||||
} else {
|
} else {
|
||||||
app.store.dispatch(doChangePath("/discover"));
|
app.store.dispatch(doChangePath("/discover"));
|
||||||
}
|
}
|
||||||
|
@ -100,27 +99,16 @@ const updateProgress = () => {
|
||||||
|
|
||||||
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() {
|
||||||
|
app.store.dispatch(doDownloadLanguages());
|
||||||
|
|
||||||
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
|
||||||
app.store.dispatch(doDaemonReady());
|
app.store.dispatch(doDaemonReady());
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<div><AuthOverlay /><App /><SnackBar /></div>
|
<div><App /><SnackBar /></div>
|
||||||
</Provider>,
|
</Provider>,
|
||||||
canvas
|
canvas
|
||||||
);
|
);
|
||||||
|
@ -129,44 +117,13 @@ var init = function() {
|
||||||
if (window.sessionStorage.getItem("loaded") == "y") {
|
if (window.sessionStorage.getItem("loaded") == "y") {
|
||||||
onDaemonReady();
|
onDaemonReady();
|
||||||
} else {
|
} else {
|
||||||
ReactDOM.render(<SplashScreen onLoadDone={onDaemonReady} />, canvas);
|
ReactDOM.render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<SplashScreen onReadyToLaunch={onDaemonReady} />
|
||||||
|
</Provider>,
|
||||||
|
canvas
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const download = (url, dest, lang, cb) => {
|
|
||||||
const file = fs.createWriteStream(dest);
|
|
||||||
const request = http.get(url, response => {
|
|
||||||
response.pipe(file);
|
|
||||||
file.on('finish', () => {
|
|
||||||
file.close(cb); // close() is async, call cb after close completes.
|
|
||||||
app.i18n.localLanguages.push(lang.replace(".json", "")); // push to our local list
|
|
||||||
});
|
|
||||||
}).on('error', err => { // Handle errors
|
|
||||||
fs.unlink(dest); // Delete the file async. (But we don't check the result)
|
|
||||||
if (cb) cb(err.message);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadLanguages = () => {
|
|
||||||
if (!fs.existsSync("app/locales")){
|
|
||||||
fs.mkdirSync("app/locales");
|
|
||||||
}
|
|
||||||
http.request({ host: 'i18n.lbry.io', path: '/' }, response => {
|
|
||||||
let str = '';
|
|
||||||
|
|
||||||
response.on('data', chunk => {
|
|
||||||
str += chunk;
|
|
||||||
});
|
|
||||||
|
|
||||||
response.on('end', () => {
|
|
||||||
const files = JSON.parse(str);JSON.parse(str);
|
|
||||||
app.i18n.localLanguages = [];
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
download(`http://i18n.lbry.io/langs/${files[i]}`, `app/locales/${files[i]}`, files[i], () => {});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).end();
|
|
||||||
};
|
|
||||||
|
|
||||||
downloadLanguages();
|
|
||||||
init();
|
init();
|
||||||
|
|
30
ui/js/page/auth/index.js
Normal file
30
ui/js/page/auth/index.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import React from "react";
|
||||||
|
import { doNavigate } from "actions/app";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { selectPathAfterAuth } from "selectors/app";
|
||||||
|
import {
|
||||||
|
selectAuthenticationIsPending,
|
||||||
|
selectEmailToVerify,
|
||||||
|
selectUserIsVerificationCandidate,
|
||||||
|
selectUser,
|
||||||
|
selectUserIsPending,
|
||||||
|
selectIdentityVerifyIsPending,
|
||||||
|
} from "selectors/user";
|
||||||
|
import AuthPage from "./view";
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
isPending:
|
||||||
|
selectAuthenticationIsPending(state) ||
|
||||||
|
selectUserIsPending(state) ||
|
||||||
|
selectIdentityVerifyIsPending(state),
|
||||||
|
email: selectEmailToVerify(state),
|
||||||
|
pathAfterAuth: selectPathAfterAuth(state),
|
||||||
|
user: selectUser(state),
|
||||||
|
isVerificationCandidate: selectUserIsVerificationCandidate(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
navigate: path => dispatch(doNavigate(path)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(AuthPage);
|
95
ui/js/page/auth/view.jsx
Normal file
95
ui/js/page/auth/view.jsx
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import React from "react";
|
||||||
|
import { BusyMessage } from "component/common";
|
||||||
|
import Link from "component/link";
|
||||||
|
import UserEmailNew from "component/userEmailNew";
|
||||||
|
import UserEmailVerify from "component/userEmailVerify";
|
||||||
|
import UserVerify from "component/userVerify";
|
||||||
|
|
||||||
|
export class AuthPage extends React.PureComponent {
|
||||||
|
componentWillMount() {
|
||||||
|
this.navigateIfAuthenticated(this.props);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
this.navigateIfAuthenticated(nextProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateIfAuthenticated(props) {
|
||||||
|
const { isPending, user } = props;
|
||||||
|
if (
|
||||||
|
!isPending &&
|
||||||
|
user &&
|
||||||
|
user.has_verified_email &&
|
||||||
|
(user.is_reward_approved || user.is_identity_verified)
|
||||||
|
) {
|
||||||
|
props.navigate(props.pathAfterAuth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getTitle() {
|
||||||
|
const { email, isPending, isVerificationCandidate, user } = this.props;
|
||||||
|
|
||||||
|
if (isPending || (user && !user.has_verified_email && !email)) {
|
||||||
|
return __("Welcome to LBRY");
|
||||||
|
} else if (user && !user.has_verified_email) {
|
||||||
|
return __("Confirm Email");
|
||||||
|
} else if (user && !user.is_identity_verified && !user.is_reward_approved) {
|
||||||
|
return __("Confirm Identity");
|
||||||
|
} else {
|
||||||
|
return __("Welcome to LBRY");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMain() {
|
||||||
|
const { email, isPending, isVerificationCandidate, user } = this.props;
|
||||||
|
|
||||||
|
if (isPending) {
|
||||||
|
return <BusyMessage message={__("Authenticating")} />;
|
||||||
|
} else if (user && !user.has_verified_email && !email) {
|
||||||
|
return <UserEmailNew />;
|
||||||
|
} else if (user && !user.has_verified_email) {
|
||||||
|
return <UserEmailVerify />;
|
||||||
|
} else if (user && !user.is_identity_verified) {
|
||||||
|
return <UserVerify />;
|
||||||
|
} else {
|
||||||
|
return <span className="empty">{__("No further steps.")}</span>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { email, user, isPending, navigate } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="">
|
||||||
|
<section className="card card--form">
|
||||||
|
<div className="card__title-primary">
|
||||||
|
<h1>{this.getTitle()}</h1>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
{!isPending &&
|
||||||
|
!email &&
|
||||||
|
user &&
|
||||||
|
!user.has_verified_email &&
|
||||||
|
<p>
|
||||||
|
{__("Create a verified identity and receive LBC rewards.")}
|
||||||
|
</p>}
|
||||||
|
{this.renderMain()}
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
<div className="help">
|
||||||
|
{__(
|
||||||
|
"This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards."
|
||||||
|
) + " "}
|
||||||
|
<Link
|
||||||
|
onClick={() => navigate("/discover")}
|
||||||
|
label={__("Return home")}
|
||||||
|
/>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthPage;
|
|
@ -3,24 +3,34 @@ import { connect } from "react-redux";
|
||||||
import { doFetchClaimsByChannel } from "actions/content";
|
import { doFetchClaimsByChannel } from "actions/content";
|
||||||
import {
|
import {
|
||||||
makeSelectClaimForUri,
|
makeSelectClaimForUri,
|
||||||
makeSelectClaimsInChannelForUri,
|
makeSelectClaimsInChannelForCurrentPage,
|
||||||
|
makeSelectFetchingChannelClaims,
|
||||||
} from "selectors/claims";
|
} from "selectors/claims";
|
||||||
|
import { selectCurrentParams } from "selectors/app";
|
||||||
|
import { doNavigate } from "actions/app";
|
||||||
|
import { makeSelectTotalPagesForChannel } from "selectors/content";
|
||||||
import ChannelPage from "./view";
|
import ChannelPage from "./view";
|
||||||
|
|
||||||
const makeSelect = () => {
|
const makeSelect = () => {
|
||||||
const selectClaim = makeSelectClaimForUri(),
|
const selectClaim = makeSelectClaimForUri(),
|
||||||
selectClaimsInChannel = makeSelectClaimsInChannelForUri();
|
selectClaimsInChannel = makeSelectClaimsInChannelForCurrentPage(),
|
||||||
|
selectFetchingChannelClaims = makeSelectFetchingChannelClaims(),
|
||||||
|
selectTotalPagesForChannel = makeSelectTotalPagesForChannel();
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
claim: selectClaim(state, props),
|
claim: selectClaim(state, props),
|
||||||
claimsInChannel: selectClaimsInChannel(state, props),
|
claimsInChannel: selectClaimsInChannel(state, props),
|
||||||
|
fetching: selectFetchingChannelClaims(state, props),
|
||||||
|
totalPages: selectTotalPagesForChannel(state, props),
|
||||||
|
params: selectCurrentParams(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
return select;
|
return select;
|
||||||
};
|
};
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
fetchClaims: uri => dispatch(doFetchClaimsByChannel(uri)),
|
fetchClaims: (uri, page) => dispatch(doFetchClaimsByChannel(uri, page)),
|
||||||
|
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(makeSelect, perform)(ChannelPage);
|
export default connect(makeSelect, perform)(ChannelPage);
|
||||||
|
|
|
@ -2,24 +2,41 @@ import React from "react";
|
||||||
import lbryuri from "lbryuri";
|
import lbryuri from "lbryuri";
|
||||||
import { BusyMessage } from "component/common";
|
import { BusyMessage } from "component/common";
|
||||||
import FileTile from "component/fileTile";
|
import FileTile from "component/fileTile";
|
||||||
|
import Link from "component/link";
|
||||||
|
import ReactPaginate from "react-paginate";
|
||||||
|
|
||||||
class ChannelPage extends React.PureComponent {
|
class ChannelPage extends React.PureComponent {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.fetchClaims(this.props);
|
const { uri, params, fetchClaims } = this.props;
|
||||||
|
|
||||||
|
fetchClaims(uri, params.page || 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
this.fetchClaims(nextProps);
|
const { params, fetching, fetchClaims } = this.props;
|
||||||
|
const nextParams = nextProps.params;
|
||||||
|
|
||||||
|
if (fetching !== nextParams.page && params.page !== nextParams.page)
|
||||||
|
fetchClaims(nextProps.uri, nextParams.page);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchClaims(props) {
|
changePage(pageNumber) {
|
||||||
if (props.claimsInChannel === undefined) {
|
const { params, currentPage } = this.props;
|
||||||
props.fetchClaims(props.uri);
|
const newParams = Object.assign({}, params, { page: pageNumber });
|
||||||
}
|
|
||||||
|
this.props.navigate("/show", newParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { claimsInChannel, claim, uri } = this.props;
|
const {
|
||||||
|
fetching,
|
||||||
|
claimsInChannel,
|
||||||
|
claim,
|
||||||
|
uri,
|
||||||
|
params,
|
||||||
|
totalPages,
|
||||||
|
} = this.props;
|
||||||
|
const { page } = params;
|
||||||
|
|
||||||
let contentList;
|
let contentList;
|
||||||
if (claimsInChannel === undefined) {
|
if (claimsInChannel === undefined) {
|
||||||
|
@ -29,14 +46,17 @@ class ChannelPage extends React.PureComponent {
|
||||||
? claimsInChannel.map(claim =>
|
? claimsInChannel.map(claim =>
|
||||||
<FileTile
|
<FileTile
|
||||||
key={claim.claim_id}
|
key={claim.claim_id}
|
||||||
uri={lbryuri.build({ name: claim.name, claimId: claim.claim_id })}
|
uri={lbryuri.build({
|
||||||
|
name: claim.name,
|
||||||
|
claimId: claim.claim_id,
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
: <span className="empty">{__("No content found.")}</span>;
|
: <span className="empty">{__("No content found.")}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="main--single-column">
|
<div>
|
||||||
<section className="card">
|
<section className="card">
|
||||||
<div className="card__inner">
|
<div className="card__inner">
|
||||||
<div className="card__title-identity"><h1>{uri}</h1></div>
|
<div className="card__title-identity"><h1>{uri}</h1></div>
|
||||||
|
@ -51,7 +71,24 @@ class ChannelPage extends React.PureComponent {
|
||||||
</section>
|
</section>
|
||||||
<h3 className="card-row__header">{__("Published Content")}</h3>
|
<h3 className="card-row__header">{__("Published Content")}</h3>
|
||||||
{contentList}
|
{contentList}
|
||||||
</main>
|
<div />
|
||||||
|
{(!fetching || (claimsInChannel && claimsInChannel.length)) &&
|
||||||
|
totalPages > 1 &&
|
||||||
|
<ReactPaginate
|
||||||
|
pageCount={totalPages}
|
||||||
|
pageRangeDisplayed={2}
|
||||||
|
previousLabel="‹"
|
||||||
|
nextLabel="›"
|
||||||
|
activeClassName="pagination__item--selected"
|
||||||
|
pageClassName="pagination__item"
|
||||||
|
previousClassName="pagination__item pagination__item--previous"
|
||||||
|
nextClassName="pagination__item pagination__item--next"
|
||||||
|
marginPagesDisplayed={2}
|
||||||
|
onPageChange={e => this.changePage(e.selected + 1)}
|
||||||
|
initialPage={parseInt(page - 1)}
|
||||||
|
containerClassName="pagination"
|
||||||
|
/>}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import lbry from "../lbry.js";
|
import lbry from "../lbry.js";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormField } from "../component/form.js";
|
import FormField from "component/formField";
|
||||||
import Link from "../component/link";
|
import Link from "../component/link";
|
||||||
|
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
|
|
@ -1,38 +1,203 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import lbryio from "lbryio.js";
|
import ReactDOM from "react-dom";
|
||||||
import lbryuri from "lbryuri";
|
import lbryuri from "lbryuri";
|
||||||
import FileCard from "component/fileCard";
|
import FileCard from "component/fileCard";
|
||||||
import { BusyMessage } from "component/common.js";
|
import { Icon, BusyMessage } from "component/common.js";
|
||||||
import ToolTip from "component/tooltip.js";
|
import ToolTip from "component/tooltip.js";
|
||||||
|
|
||||||
const FeaturedCategory = props => {
|
class FeaturedCategory extends React.PureComponent {
|
||||||
const { category, names } = props;
|
componentWillMount() {
|
||||||
|
this.setState({
|
||||||
|
numItems: this.props.names.length,
|
||||||
|
canScrollPrevious: false,
|
||||||
|
canScrollNext: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
handleScrollPrevious() {
|
||||||
<div className="card-row card-row--small">
|
const cardRow = ReactDOM.findDOMNode(this.refs.rowitems);
|
||||||
<h3 className="card-row__header">
|
if (cardRow.scrollLeft > 0) {
|
||||||
{category}
|
// check the visible cards
|
||||||
{category &&
|
const cards = cardRow.getElementsByTagName("section");
|
||||||
category.match(/^community/i) &&
|
let firstVisibleCard = null;
|
||||||
<ToolTip
|
let firstVisibleIdx = -1;
|
||||||
label={__("What's this?")}
|
for (var i = 0; i < cards.length; i++) {
|
||||||
body={__(
|
if (this.isCardVisible(cards[i], cardRow, false)) {
|
||||||
'Community Content is a public space where anyone can share content with the rest of the LBRY community. Bid on the names "one," "two," "three," "four" and "five" to put your content here!'
|
firstVisibleCard = cards[i];
|
||||||
)}
|
firstVisibleIdx = i;
|
||||||
className="tooltip--header"
|
break;
|
||||||
/>}
|
}
|
||||||
</h3>
|
}
|
||||||
{names &&
|
|
||||||
names.map(name =>
|
const numDisplayed = this.numDisplayedCards(cardRow);
|
||||||
<FileCard
|
const scrollToIdx = firstVisibleIdx - numDisplayed;
|
||||||
key={name}
|
const animationCallback = () => {
|
||||||
displayStyle="card"
|
this.setState({
|
||||||
uri={lbryuri.normalize(name)}
|
canScrollPrevious: cardRow.scrollLeft !== 0,
|
||||||
/>
|
canScrollNext: true,
|
||||||
)}
|
});
|
||||||
</div>
|
};
|
||||||
);
|
this.scrollCardItemsLeftAnimated(
|
||||||
};
|
cardRow,
|
||||||
|
scrollToIdx < 0 ? 0 : cards[scrollToIdx].offsetLeft,
|
||||||
|
100,
|
||||||
|
animationCallback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScrollNext() {
|
||||||
|
const cardRow = ReactDOM.findDOMNode(this.refs.rowitems);
|
||||||
|
|
||||||
|
// check the visible cards
|
||||||
|
const cards = cardRow.getElementsByTagName("section");
|
||||||
|
let lastVisibleCard = null;
|
||||||
|
let lastVisibleIdx = -1;
|
||||||
|
for (var i = 0; i < cards.length; i++) {
|
||||||
|
if (this.isCardVisible(cards[i], cardRow, true)) {
|
||||||
|
lastVisibleCard = cards[i];
|
||||||
|
lastVisibleIdx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastVisibleCard) {
|
||||||
|
const numDisplayed = this.numDisplayedCards(cardRow);
|
||||||
|
const animationCallback = () => {
|
||||||
|
// update last visible index after scroll
|
||||||
|
for (var i = 0; i < cards.length; i++) {
|
||||||
|
if (this.isCardVisible(cards[i], cardRow, true)) {
|
||||||
|
lastVisibleIdx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ canScrollPrevious: true });
|
||||||
|
if (lastVisibleIdx === cards.length - 1) {
|
||||||
|
this.setState({ canScrollNext: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.scrollCardItemsLeftAnimated(
|
||||||
|
cardRow,
|
||||||
|
Math.min(
|
||||||
|
lastVisibleCard.offsetLeft,
|
||||||
|
cardRow.scrollWidth - cardRow.clientWidth
|
||||||
|
),
|
||||||
|
100,
|
||||||
|
animationCallback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollCardItemsLeftAnimated(cardRow, target, duration, callback) {
|
||||||
|
if (!duration || duration <= diff) {
|
||||||
|
cardRow.scrollLeft = target;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const component = this;
|
||||||
|
const diff = target - cardRow.scrollLeft;
|
||||||
|
const tick = diff / duration * 10;
|
||||||
|
setTimeout(() => {
|
||||||
|
cardRow.scrollLeft = cardRow.scrollLeft + tick;
|
||||||
|
if (cardRow.scrollLeft === target) {
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
component.scrollCardItemsLeftAnimated(
|
||||||
|
cardRow,
|
||||||
|
target,
|
||||||
|
duration - 10,
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
isCardVisible(section, cardRow, partialVisibility) {
|
||||||
|
// check if a card is fully or partialy visible in its parent
|
||||||
|
const cardRowWidth = cardRow.offsetWidth;
|
||||||
|
const cardRowLeft = cardRow.scrollLeft;
|
||||||
|
const cardRowEnd = cardRowLeft + cardRow.offsetWidth;
|
||||||
|
const sectionLeft = section.offsetLeft - cardRowLeft;
|
||||||
|
const sectionEnd = sectionLeft + section.offsetWidth;
|
||||||
|
|
||||||
|
return (
|
||||||
|
(sectionLeft >= 0 && sectionEnd <= cardRowWidth) ||
|
||||||
|
(((sectionLeft < 0 && sectionEnd > 0) ||
|
||||||
|
(sectionLeft > 0 && sectionLeft <= cardRowWidth)) &&
|
||||||
|
partialVisibility)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
numDisplayedCards(cardRow) {
|
||||||
|
const cards = cardRow.getElementsByTagName("section");
|
||||||
|
const cardRowWidth = cardRow.offsetWidth;
|
||||||
|
// get the width of the first card and then calculate
|
||||||
|
const cardWidth = cards.length > 0 ? cards[0].offsetWidth : 0;
|
||||||
|
|
||||||
|
if (cardWidth > 0) {
|
||||||
|
return Math.ceil(cardRowWidth / cardWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a default value of 1 card displayed if the card width couldn't be determined
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { category, names } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card-row card-row--small">
|
||||||
|
<h3 className="card-row__header">
|
||||||
|
{category}
|
||||||
|
{category &&
|
||||||
|
category.match(/^community/i) &&
|
||||||
|
<ToolTip
|
||||||
|
label={__("What's this?")}
|
||||||
|
body={__(
|
||||||
|
'Community Content is a public space where anyone can share content with the rest of the LBRY community. Bid on the names "one," "two," "three," "four" and "five" to put your content here!'
|
||||||
|
)}
|
||||||
|
className="tooltip--header"
|
||||||
|
/>}
|
||||||
|
</h3>
|
||||||
|
<div className="card-row__scrollhouse">
|
||||||
|
{this.state.canScrollPrevious &&
|
||||||
|
<div className="card-row__nav card-row__nav--left">
|
||||||
|
<a
|
||||||
|
className="card-row__scroll-button"
|
||||||
|
onClick={this.handleScrollPrevious.bind(this)}
|
||||||
|
>
|
||||||
|
<Icon icon="icon-chevron-left" />
|
||||||
|
</a>
|
||||||
|
</div>}
|
||||||
|
{this.state.canScrollNext &&
|
||||||
|
<div className="card-row__nav card-row__nav--right">
|
||||||
|
<a
|
||||||
|
className="card-row__scroll-button"
|
||||||
|
onClick={this.handleScrollNext.bind(this)}
|
||||||
|
>
|
||||||
|
<Icon icon="icon-chevron-right" />
|
||||||
|
</a>
|
||||||
|
</div>}
|
||||||
|
<div ref="rowitems" className="card-row__items">
|
||||||
|
{names &&
|
||||||
|
names.map(name =>
|
||||||
|
<FileCard
|
||||||
|
key={name}
|
||||||
|
displayStyle="card"
|
||||||
|
uri={lbryuri.normalize(name)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class DiscoverPage extends React.PureComponent {
|
class DiscoverPage extends React.PureComponent {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
|
@ -45,16 +210,20 @@ class DiscoverPage extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { featuredUris, fetchingFeaturedUris } = this.props;
|
const { featuredUris, fetchingFeaturedUris } = this.props;
|
||||||
const failedToLoad =
|
const hasContent =
|
||||||
!fetchingFeaturedUris &&
|
typeof featuredUris === "object" && Object.keys(featuredUris).length,
|
||||||
(featuredUris === undefined ||
|
failedToLoad = !fetchingFeaturedUris && !hasContent;
|
||||||
(featuredUris !== undefined && Object.keys(featuredUris).length === 0));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<main
|
||||||
{fetchingFeaturedUris &&
|
className={
|
||||||
|
hasContent && fetchingFeaturedUris ? "main--refreshing" : null
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{!hasContent &&
|
||||||
|
fetchingFeaturedUris &&
|
||||||
<BusyMessage message={__("Fetching content")} />}
|
<BusyMessage message={__("Fetching content")} />}
|
||||||
{typeof featuredUris === "object" &&
|
{hasContent &&
|
||||||
Object.keys(featuredUris).map(
|
Object.keys(featuredUris).map(
|
||||||
category =>
|
category =>
|
||||||
featuredUris[category].length
|
featuredUris[category].length
|
||||||
|
|
|
@ -3,15 +3,22 @@ import { connect } from "react-redux";
|
||||||
import { doFetchFileInfosAndPublishedClaims } from "actions/file_info";
|
import { doFetchFileInfosAndPublishedClaims } from "actions/file_info";
|
||||||
import {
|
import {
|
||||||
selectFileInfosDownloaded,
|
selectFileInfosDownloaded,
|
||||||
selectFileListDownloadedOrPublishedIsPending,
|
selectIsFetchingFileListDownloadedOrPublished,
|
||||||
} from "selectors/file_info";
|
} from "selectors/file_info";
|
||||||
|
import {
|
||||||
|
selectMyClaimsWithoutChannels,
|
||||||
|
selectIsFetchingClaimListMine,
|
||||||
|
} from "selectors/claims";
|
||||||
|
import { doFetchClaimListMine } from "actions/content";
|
||||||
import { doNavigate } from "actions/app";
|
import { doNavigate } from "actions/app";
|
||||||
import { doCancelAllResolvingUris } from "actions/content";
|
import { doCancelAllResolvingUris } from "actions/content";
|
||||||
import FileListDownloaded from "./view";
|
import FileListDownloaded from "./view";
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
fileInfos: selectFileInfosDownloaded(state),
|
fileInfos: selectFileInfosDownloaded(state),
|
||||||
isPending: selectFileListDownloadedOrPublishedIsPending(state),
|
isFetching: selectIsFetchingFileListDownloadedOrPublished(state),
|
||||||
|
claims: selectMyClaimsWithoutChannels(state),
|
||||||
|
isFetchingClaims: selectIsFetchingClaimListMine(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
@ -19,6 +26,7 @@ const perform = dispatch => ({
|
||||||
fetchFileInfosDownloaded: () =>
|
fetchFileInfosDownloaded: () =>
|
||||||
dispatch(doFetchFileInfosAndPublishedClaims()),
|
dispatch(doFetchFileInfosAndPublishedClaims()),
|
||||||
cancelResolvingUris: () => dispatch(doCancelAllResolvingUris()),
|
cancelResolvingUris: () => dispatch(doCancelAllResolvingUris()),
|
||||||
|
fetchClaims: () => dispatch(doFetchClaimListMine()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(FileListDownloaded);
|
export default connect(select, perform)(FileListDownloaded);
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import lbry from "lbry.js";
|
|
||||||
import lbryuri from "lbryuri.js";
|
|
||||||
import Link from "component/link";
|
import Link from "component/link";
|
||||||
import { FormField } from "component/form.js";
|
|
||||||
import { FileTile } from "component/fileTile";
|
import { FileTile } from "component/fileTile";
|
||||||
import rewards from "rewards.js";
|
|
||||||
import lbryio from "lbryio.js";
|
|
||||||
import { BusyMessage, Thumbnail } from "component/common.js";
|
import { BusyMessage, Thumbnail } from "component/common.js";
|
||||||
import FileList from "component/fileList";
|
import FileList from "component/fileList";
|
||||||
import SubHeader from "component/subHeader";
|
import SubHeader from "component/subHeader";
|
||||||
|
|
||||||
class FileListDownloaded extends React.PureComponent {
|
class FileListDownloaded extends React.PureComponent {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
if (!this.props.isPending) this.props.fetchFileInfosDownloaded();
|
if (!this.props.isFetchingClaims) this.props.fetchClaims();
|
||||||
|
if (!this.props.isFetching) this.props.fetchFileInfosDownloaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -20,13 +16,13 @@ class FileListDownloaded extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { fileInfos, isPending, navigate } = this.props;
|
const { fileInfos, isFetching, navigate } = this.props;
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
if (fileInfos && fileInfos.length > 0) {
|
if (fileInfos && fileInfos.length > 0) {
|
||||||
content = <FileList fileInfos={fileInfos} fetching={isPending} />;
|
content = <FileList fileInfos={fileInfos} fetching={isFetching} />;
|
||||||
} else {
|
} else {
|
||||||
if (isPending) {
|
if (isFetching) {
|
||||||
content = <BusyMessage message={__("Loading")} />;
|
content = <BusyMessage message={__("Loading")} />;
|
||||||
} else {
|
} else {
|
||||||
content = (
|
content = (
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue