Merge branch 'master' into restructuring
This commit is contained in:
commit
eaa5c212d2
23 changed files with 284 additions and 72 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -24,3 +24,5 @@ build/daemon.zip
|
|||
.vimrc
|
||||
|
||||
package-lock.json
|
||||
|
||||
.DS_Store
|
||||
|
|
|
@ -21,6 +21,7 @@ Web UI version numbers should always match the corresponding version of LBRY App
|
|||
* Fixed scriolling restore/reset/set (#729)
|
||||
* Fixed sorting by title for published files (#614)
|
||||
* App now uses the new balance_delta field in the txn list.
|
||||
* Abandoning from the claim page now works.
|
||||
*
|
||||
|
||||
### Deprecated
|
||||
|
|
17
src/renderer/.flowconfig
Normal file
17
src/renderer/.flowconfig
Normal file
|
@ -0,0 +1,17 @@
|
|||
[ignore]
|
||||
.*/node_modules/**
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
flow-typed
|
||||
|
||||
[lints]
|
||||
|
||||
[options]
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
|
||||
module.name_mapper='^constants\(.*\)$' -> '<PROJECT_ROOT>/js/constants\1'
|
||||
module.name_mapper='^redux\(.*\)$' -> '<PROJECT_ROOT>/js/redux\1'
|
||||
|
||||
[strict]
|
|
@ -1,13 +1,14 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { formatCredits, formatFullPrice } from "util/formatCredits";
|
||||
import lbry from "../lbry.js";
|
||||
|
||||
//component/icon.js
|
||||
export class Icon extends React.PureComponent {
|
||||
static propTypes = {
|
||||
icon: React.PropTypes.string.isRequired,
|
||||
className: React.PropTypes.string,
|
||||
fixed: React.PropTypes.bool,
|
||||
icon: PropTypes.string.isRequired,
|
||||
className: PropTypes.string,
|
||||
fixed: PropTypes.bool,
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -24,7 +25,7 @@ export class Icon extends React.PureComponent {
|
|||
|
||||
export class TruncatedText extends React.PureComponent {
|
||||
static propTypes = {
|
||||
lines: React.PropTypes.number,
|
||||
lines: PropTypes.number,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -45,7 +46,7 @@ export class TruncatedText extends React.PureComponent {
|
|||
|
||||
export class BusyMessage extends React.PureComponent {
|
||||
static propTypes = {
|
||||
message: React.PropTypes.string,
|
||||
message: PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -65,14 +66,14 @@ export class CurrencySymbol extends React.PureComponent {
|
|||
|
||||
export class CreditAmount extends React.PureComponent {
|
||||
static propTypes = {
|
||||
amount: React.PropTypes.number.isRequired,
|
||||
precision: React.PropTypes.number,
|
||||
isEstimate: React.PropTypes.bool,
|
||||
label: React.PropTypes.bool,
|
||||
showFree: React.PropTypes.bool,
|
||||
showFullPrice: React.PropTypes.bool,
|
||||
showPlus: React.PropTypes.bool,
|
||||
look: React.PropTypes.oneOf(["indicator", "plain", "fee"]),
|
||||
amount: PropTypes.number.isRequired,
|
||||
precision: PropTypes.number,
|
||||
isEstimate: PropTypes.bool,
|
||||
label: PropTypes.bool,
|
||||
showFree: PropTypes.bool,
|
||||
showFullPrice: PropTypes.bool,
|
||||
showPlus: PropTypes.bool,
|
||||
look: PropTypes.oneOf(["indicator", "plain", "fee"]),
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -142,7 +143,7 @@ let addressStyle = {
|
|||
};
|
||||
export class Address extends React.PureComponent {
|
||||
static propTypes = {
|
||||
address: React.PropTypes.string,
|
||||
address: PropTypes.string,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -174,7 +175,7 @@ export class Address extends React.PureComponent {
|
|||
|
||||
export class Thumbnail extends React.PureComponent {
|
||||
static propTypes = {
|
||||
src: React.PropTypes.string,
|
||||
src: PropTypes.string,
|
||||
};
|
||||
|
||||
handleError() {
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const { remote } = require("electron");
|
||||
class FileSelector extends React.PureComponent {
|
||||
static propTypes = {
|
||||
type: React.PropTypes.oneOf(["file", "directory"]),
|
||||
initPath: React.PropTypes.string,
|
||||
onFileChosen: React.PropTypes.func,
|
||||
type: PropTypes.oneOf(["file", "directory"]),
|
||||
initPath: PropTypes.string,
|
||||
onFileChosen: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import FormField from "component/formField";
|
||||
import { Icon } from "component/common.js";
|
||||
|
||||
|
@ -12,7 +13,7 @@ export function formFieldId() {
|
|||
|
||||
export class Form extends React.PureComponent {
|
||||
static propTypes = {
|
||||
onSubmit: React.PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -35,15 +36,9 @@ export class Form extends React.PureComponent {
|
|||
|
||||
export class FormRow extends React.PureComponent {
|
||||
static propTypes = {
|
||||
label: React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.element,
|
||||
]),
|
||||
errorMessage: React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.object,
|
||||
]),
|
||||
// helper: React.PropTypes.html,
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||
errorMessage: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
// helper: PropTypes.html,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import FileSelector from "component/file-selector.js";
|
||||
import SimpleMDE from "react-simplemde-editor";
|
||||
import { formFieldNestedLabelTypes, formFieldId } from "../form";
|
||||
|
@ -8,14 +9,14 @@ 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,
|
||||
trim: React.PropTypes.bool,
|
||||
regexp: React.PropTypes.oneOfType([
|
||||
React.PropTypes.instanceOf(RegExp),
|
||||
React.PropTypes.string,
|
||||
type: PropTypes.string.isRequired,
|
||||
prefix: PropTypes.string,
|
||||
postfix: PropTypes.string,
|
||||
hasError: PropTypes.bool,
|
||||
trim: PropTypes.bool,
|
||||
regexp: PropTypes.oneOfType([
|
||||
PropTypes.instanceOf(RegExp),
|
||||
PropTypes.string,
|
||||
]),
|
||||
};
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import * as icons from "constants/icons";
|
||||
|
||||
export default class Icon extends React.PureComponent {
|
||||
static propTypes = {
|
||||
icon: React.PropTypes.string.isRequired,
|
||||
fixed: React.PropTypes.bool,
|
||||
icon: PropTypes.string.isRequired,
|
||||
fixed: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import lbry from "../lbry.js";
|
||||
import { BusyMessage, Icon } from "./common.js";
|
||||
import Link from "component/link";
|
||||
|
||||
class LoadScreen extends React.PureComponent {
|
||||
static propTypes = {
|
||||
message: React.PropTypes.string.isRequired,
|
||||
details: React.PropTypes.string,
|
||||
isWarning: React.PropTypes.bool,
|
||||
message: PropTypes.string.isRequired,
|
||||
details: PropTypes.string,
|
||||
isWarning: PropTypes.bool,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Icon } from "./common.js";
|
||||
import Link from "component/link";
|
||||
|
||||
export class DropDownMenuItem extends React.PureComponent {
|
||||
static propTypes = {
|
||||
href: React.PropTypes.string,
|
||||
label: React.PropTypes.string,
|
||||
icon: React.PropTypes.string,
|
||||
onClick: React.PropTypes.func,
|
||||
href: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import PublishForm from "./view";
|
||||
import { selectBalance } from "redux/selectors/wallet";
|
||||
|
||||
export default connect(null, null)(PublishForm);
|
||||
const select = state => ({
|
||||
balance: selectBalance(state),
|
||||
});
|
||||
|
||||
export default connect(select, null)(PublishForm);
|
||||
|
|
|
@ -48,11 +48,22 @@ class ChannelSection extends React.PureComponent {
|
|||
|
||||
handleNewChannelBidChange(event) {
|
||||
this.setState({
|
||||
newChannelBid: event.target.value,
|
||||
newChannelBid: parseFloat(event.target.value),
|
||||
});
|
||||
}
|
||||
|
||||
handleCreateChannelClick(event) {
|
||||
const { balance } = this.props;
|
||||
const { newChannelBid } = this.state;
|
||||
|
||||
if (newChannelBid > balance) {
|
||||
this.refs.newChannelName.showError(
|
||||
__("Unable to create channel due to insufficient funds.")
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
creatingChannel: true,
|
||||
});
|
||||
|
|
|
@ -61,6 +61,15 @@ class PublishForm extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleSubmit() {
|
||||
const { balance } = this.props;
|
||||
const { bid } = this.state;
|
||||
|
||||
if (bid > balance) {
|
||||
this.handlePublishError({ message: "insufficient funds" });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
submitting: true,
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import lbry from "lbry.js";
|
||||
import LoadScreen from "../load_screen.js";
|
||||
import ModalIncompatibleDaemon from "modal/modalIncompatibleDaemon";
|
||||
|
@ -8,8 +9,8 @@ import * as modals from "constants/modal_types";
|
|||
|
||||
export class SplashScreen extends React.PureComponent {
|
||||
static propTypes = {
|
||||
message: React.PropTypes.string,
|
||||
onLoadDone: React.PropTypes.func,
|
||||
message: PropTypes.string,
|
||||
onLoadDone: PropTypes.func,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export class ToolTip extends React.PureComponent {
|
||||
static propTypes = {
|
||||
body: React.PropTypes.string.isRequired,
|
||||
label: React.PropTypes.string.isRequired,
|
||||
body: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
|
||||
class TruncatedMarkdown extends React.PureComponent {
|
||||
static propTypes = {
|
||||
lines: React.PropTypes.number,
|
||||
lines: PropTypes.number,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import lbryuri from "lbryuri.js";
|
||||
import { Icon } from "component/common.js";
|
||||
import { parseQueryParams } from "util/query_params";
|
||||
|
@ -7,8 +8,8 @@ class WunderBar extends React.PureComponent {
|
|||
static TYPING_TIMEOUT = 800;
|
||||
|
||||
static propTypes = {
|
||||
onSearch: React.PropTypes.func.isRequired,
|
||||
onSubmit: React.PropTypes.func.isRequired,
|
||||
onSearch: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
3
src/renderer/flow-typed/electron.js
vendored
Normal file
3
src/renderer/flow-typed/electron.js
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
declare module 'electron' {
|
||||
declare module.exports: any;
|
||||
}
|
116
src/renderer/flowtype-plugin.js
Normal file
116
src/renderer/flowtype-plugin.js
Normal file
|
@ -0,0 +1,116 @@
|
|||
var spawnSync = require('child_process').spawnSync;
|
||||
var flow = require('flow-bin');
|
||||
var merge = require('lodash.merge');
|
||||
|
||||
var store = {
|
||||
error: null,
|
||||
flowOptions: [
|
||||
'status',
|
||||
'--color=always',
|
||||
],
|
||||
options: {
|
||||
warn: false,
|
||||
|
||||
formatter: function (errorCode, errorDetails) {
|
||||
return 'Flow: ' + errorCode + '\n\n' + errorDetails;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
function flowErrorCode(status) {
|
||||
var error;
|
||||
switch (status) {
|
||||
/*
|
||||
case 0:
|
||||
error = null;
|
||||
break;
|
||||
*/
|
||||
case 1:
|
||||
error = 'Server Initializing';
|
||||
break;
|
||||
case 2:
|
||||
error = 'Type Error';
|
||||
break;
|
||||
case 3:
|
||||
error = 'Out of Time';
|
||||
break;
|
||||
case 4:
|
||||
error = 'Kill Error';
|
||||
break;
|
||||
case 6:
|
||||
error = 'No Server Running';
|
||||
break;
|
||||
case 7:
|
||||
error = 'Out of Retries';
|
||||
break;
|
||||
case 8:
|
||||
error = 'Invalid Flowconfig';
|
||||
break;
|
||||
case 9:
|
||||
error = 'Build Id Mismatch';
|
||||
break;
|
||||
case 10:
|
||||
error = 'Input Error';
|
||||
break;
|
||||
case 11:
|
||||
error = 'Lock Stolen';
|
||||
break;
|
||||
case 12:
|
||||
error = 'Could Not Find Flowconfig';
|
||||
break;
|
||||
case 13:
|
||||
error = 'Server Out of Date';
|
||||
break;
|
||||
case 14:
|
||||
error = 'Server Client Directory Mismatch';
|
||||
break;
|
||||
case 15:
|
||||
error = 'Out of Shared Memory';
|
||||
break;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
function checkFlowStatus(compiler, next) {
|
||||
var res = spawnSync(flow, store.flowOptions);
|
||||
var status = res.status;
|
||||
|
||||
if (status !== 0) {
|
||||
var errorCode = flowErrorCode(status);
|
||||
var errorDetails = res.stdout.toString() + res.stderr.toString();
|
||||
|
||||
store.error = new Error(store.options.formatter(errorCode, errorDetails));
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
|
||||
function pushError(compilation) {
|
||||
if (store.error) {
|
||||
if (store.options.warn) {
|
||||
compilation.warnings.push(store.error);
|
||||
} else {
|
||||
compilation.errors.push(store.error);
|
||||
}
|
||||
|
||||
store.error = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function FlowFlowPlugin(options) {
|
||||
store.options = merge(store.options, options);
|
||||
}
|
||||
|
||||
FlowFlowPlugin.prototype.apply = function(compiler) {
|
||||
compiler.plugin('run', checkFlowStatus);
|
||||
compiler.plugin('watch-run', checkFlowStatus);
|
||||
|
||||
compiler.plugin('compilation', pushError);
|
||||
};
|
||||
|
||||
module.exports = FlowFlowPlugin;
|
|
@ -1,18 +1,19 @@
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import ReactModal from "react-modal";
|
||||
import Link from "component/link/index";
|
||||
import app from "app.js";
|
||||
|
||||
export class Modal extends React.PureComponent {
|
||||
static propTypes = {
|
||||
type: React.PropTypes.oneOf(["alert", "confirm", "custom"]),
|
||||
overlay: React.PropTypes.bool,
|
||||
onConfirmed: React.PropTypes.func,
|
||||
onAborted: React.PropTypes.func,
|
||||
confirmButtonLabel: React.PropTypes.string,
|
||||
abortButtonLabel: React.PropTypes.string,
|
||||
confirmButtonDisabled: React.PropTypes.bool,
|
||||
abortButtonDisabled: React.PropTypes.bool,
|
||||
type: PropTypes.oneOf(["alert", "confirm", "custom"]),
|
||||
overlay: PropTypes.bool,
|
||||
onConfirmed: PropTypes.func,
|
||||
onAborted: PropTypes.func,
|
||||
confirmButtonLabel: PropTypes.string,
|
||||
abortButtonLabel: PropTypes.string,
|
||||
confirmButtonDisabled: PropTypes.bool,
|
||||
abortButtonDisabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -64,8 +65,8 @@ export class Modal extends React.PureComponent {
|
|||
|
||||
export class ExpandableModal extends React.PureComponent {
|
||||
static propTypes = {
|
||||
expandButtonLabel: React.PropTypes.string,
|
||||
extraContent: React.PropTypes.element,
|
||||
expandButtonLabel: PropTypes.string,
|
||||
extraContent: PropTypes.element,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
|
@ -102,8 +102,8 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) {
|
|||
const fileInfo = byOutpoint[outpoint];
|
||||
|
||||
if (fileInfo) {
|
||||
txid = fileInfo.outpoint.slice(0, -2);
|
||||
nout = fileInfo.outpoint.slice(-1);
|
||||
const txid = fileInfo.outpoint.slice(0, -2);
|
||||
const nout = fileInfo.outpoint.slice(-1);
|
||||
|
||||
dispatch(doAbandonClaim(txid, nout));
|
||||
}
|
||||
|
|
|
@ -1,22 +1,60 @@
|
|||
// @flow
|
||||
|
||||
import * as types from "constants/action_types";
|
||||
import * as modalTypes from "constants/modal_types";
|
||||
|
||||
const { remote } = require("electron");
|
||||
|
||||
const application = remote.app;
|
||||
const win = remote.BrowserWindow.getFocusedWindow();
|
||||
|
||||
const reducers = {};
|
||||
const defaultState = {
|
||||
|
||||
export type SnackBar = {
|
||||
message: string,
|
||||
linkText: string,
|
||||
linkTarget: string,
|
||||
isError: boolean,
|
||||
};
|
||||
export type AppState = {
|
||||
isLoaded: boolean,
|
||||
modal: ?string,
|
||||
modalProps: mixed,
|
||||
platform: string,
|
||||
upgradeSkipped: boolean,
|
||||
daemonVersionMatched: ?boolean,
|
||||
daemonReady: boolean,
|
||||
hasSignature: boolean,
|
||||
badgeNumber: number,
|
||||
volume: number,
|
||||
downloadProgress: ?number,
|
||||
upgradeDownloading: ?boolean,
|
||||
upgradeDownloadComplete: ?boolean,
|
||||
checkUpgradeTimer: ?number,
|
||||
isUpgradeAvailable: ?boolean,
|
||||
isUpgradeSkipped: ?boolean,
|
||||
snackBar: ?SnackBar,
|
||||
};
|
||||
|
||||
const defaultState: AppState = {
|
||||
isLoaded: false,
|
||||
modal: null,
|
||||
modalProps: {},
|
||||
platform: process.platform,
|
||||
upgradeSkipped: sessionStorage.getItem("upgradeSkipped"),
|
||||
upgradeSkipped: sessionStorage.getItem("upgradeSkipped") === "true",
|
||||
daemonVersionMatched: null,
|
||||
daemonReady: false,
|
||||
hasSignature: false,
|
||||
badgeNumber: 0,
|
||||
volume: sessionStorage.getItem("volume") || 1,
|
||||
volume: Number(sessionStorage.getItem("volume")) || 1,
|
||||
|
||||
downloadProgress: undefined,
|
||||
upgradeDownloading: undefined,
|
||||
upgradeDownloadComplete: undefined,
|
||||
checkUpgradeTimer: undefined,
|
||||
isUpgradeAvailable: undefined,
|
||||
isUpgradeSkipped: undefined,
|
||||
snackBar: undefined,
|
||||
};
|
||||
|
||||
reducers[types.DAEMON_READY] = function(state, action) {
|
||||
|
@ -61,7 +99,7 @@ reducers[types.UPGRADE_DOWNLOAD_STARTED] = function(state, action) {
|
|||
};
|
||||
|
||||
reducers[types.SKIP_UPGRADE] = function(state, action) {
|
||||
sessionStorage.setItem("upgradeSkipped", true);
|
||||
sessionStorage.setItem("upgradeSkipped", "true");
|
||||
|
||||
return Object.assign({}, state, {
|
||||
isUpgradeSkipped: true,
|
||||
|
@ -164,7 +202,7 @@ reducers[types.VOLUME_CHANGED] = function(state, action) {
|
|||
});
|
||||
};
|
||||
|
||||
export default function reducer(state = defaultState, action) {
|
||||
export default function reducer(state: AppState = defaultState, action: any) {
|
||||
const handler = reducers[action.type];
|
||||
if (handler) return handler(state, action);
|
||||
return state;
|
||||
|
|
|
@ -22,7 +22,7 @@ function rewardMessage(type, amount) {
|
|||
amount
|
||||
),
|
||||
many_downloads: __(
|
||||
"You earned %s LBC for downloading some of the things.",
|
||||
"You earned %s LBC for downloading a bunch of things.",
|
||||
amount
|
||||
),
|
||||
first_publish: __(
|
||||
|
@ -33,6 +33,10 @@ function rewardMessage(type, amount) {
|
|||
"You earned %s LBC for watching a featured download.",
|
||||
amount
|
||||
),
|
||||
referral: __(
|
||||
"You earned %s LBC for referring someone.",
|
||||
amount
|
||||
),
|
||||
}[type];
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue