Merge branch 'master' into master

This commit is contained in:
Baltazar Gomez 2017-08-24 12:28:05 -06:00 committed by GitHub
commit 03b04ad9b0
24 changed files with 462 additions and 62 deletions

2
.gitignore vendored
View file

@ -10,8 +10,10 @@
/app/dist /app/dist
/app/locales
/app/node_modules /app/node_modules
/build/venv /build/venv
/build/daemon.ver
/lbry-app-venv /lbry-app-venv
/lbry-app /lbry-app
/lbry-venv /lbry-venv

View file

@ -82,9 +82,17 @@ fi
DAEMON_VER=$(node -e "console.log(require(\"$ROOT/app/package.json\").lbrySettings.lbrynetDaemonVersion)") DAEMON_VER=$(node -e "console.log(require(\"$ROOT/app/package.json\").lbrySettings.lbrynetDaemonVersion)")
DAEMON_URL_TEMPLATE=$(node -e "console.log(require(\"$ROOT/app/package.json\").lbrySettings.lbrynetDaemonUrlTemplate)") DAEMON_URL_TEMPLATE=$(node -e "console.log(require(\"$ROOT/app/package.json\").lbrySettings.lbrynetDaemonUrlTemplate)")
DAEMON_URL=$(echo ${DAEMON_URL_TEMPLATE//DAEMONVER/$DAEMON_VER} | sed "s/OSNAME/$OSNAME/g") DAEMON_URL=$(echo ${DAEMON_URL_TEMPLATE//DAEMONVER/$DAEMON_VER} | sed "s/OSNAME/$OSNAME/g")
wget --quiet "$DAEMON_URL" -O "$BUILD_DIR/daemon.zip" DAEMON_VER_PATH="$BUILD_DIR/daemon.ver"
unzip "$BUILD_DIR/daemon.zip" -d "$ROOT/app/dist/" echo "$DAEMON_VER_PATH"
rm "$BUILD_DIR/daemon.zip" if [[ ! -f $DAEMON_VER_PATH || ! -f $ROOT/app/dist/lbrynet-daemon || "$(< "$DAEMON_VER_PATH")" != "$DAEMON_VER" ]]; then
wget --quiet "$DAEMON_URL" -O "$BUILD_DIR/daemon.zip"
unzip "$BUILD_DIR/daemon.zip" -d "$ROOT/app/dist/"
rm "$BUILD_DIR/daemon.zip"
echo "$DAEMON_VER" > "$DAEMON_VER_PATH"
else
echo "Already have daemon version $DAEMON_VER, skipping download"
fi

View file

@ -231,14 +231,15 @@ export function doStartDownload(uri, outpoint) {
return function(dispatch, getState) { return function(dispatch, getState) {
const state = getState(); const state = getState();
if (!outpoint) { throw new Error("outpoint is required to begin a download"); } if (!outpoint) {
throw new Error("outpoint is required to begin a download");
}
const { downloadingByOutpoint = {} } = state.fileInfo; const { downloadingByOutpoint = {} } = state.fileInfo;
if (downloadingByOutpoint[outpoint]) return; if (downloadingByOutpoint[outpoint]) return;
lbry.file_list({ outpoint, full_status: true }).then(([fileInfo]) => { lbry.file_list({ outpoint, full_status: true }).then(([fileInfo]) => {
dispatch({ dispatch({
type: types.DOWNLOADING_STARTED, type: types.DOWNLOADING_STARTED,
data: { data: {
@ -282,29 +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(doAlertError(error));
dispatch(doOpenModal("timedOut"));
} else {
dispatch(doDownloadFile(uri, streamInfo));
}
}).catch(error => {
dispatch({
type: types.LOADING_VIDEO_FAILED,
data: { uri },
}); });
dispatch(doAlertError(error));
});
}; };
} }

View file

@ -1,6 +1,10 @@
import * as types from "constants/action_types"; import * as types from "constants/action_types";
import * as settings from "constants/settings"; import * as settings from "constants/settings";
import batchActions from "util/batchActions";
import lbry from "lbry"; import lbry from "lbry";
import fs from "fs";
import http from "http";
const { remote } = require("electron"); const { remote } = require("electron");
const { extname } = require("path"); const { extname } = require("path");
@ -84,5 +88,97 @@ export function doSetTheme(name) {
// update theme // update theme
dispatch(doSetClientSetting(settings.THEME, theme.name)); dispatch(doSetClientSetting(settings.THEME, theme.name));
} }
}
export function doDownloadLanguage(langFile) {
return function(dispatch, getState) {
const destinationPath = `app/locales/${langFile}`;
const language = langFile.replace(".json", "");
const req = http.get(
{
headers: {
"Content-Type": "text/html",
},
host: "i18n.lbry.io",
path: `/langs/${langFile}`,
},
response => {
if (response.statusCode === 200) {
const file = fs.createWriteStream(destinationPath);
file.on("error", errorHandler);
file.on("finish", () => {
file.close();
// push to our local list
dispatch({
type: types.DOWNLOAD_LANGUAGE_SUCCEEDED,
data: { language: language },
});
});
response.pipe(file);
} else {
errorHandler(new Error("Language request failed."));
}
}
);
const errorHandler = err => {
fs.unlink(destinationPath, () => {}); // Delete the file async. (But we don't check the result)
dispatch({
type: types.DOWNLOAD_LANGUAGE_FAILED,
data: { language },
});
};
req.setTimeout(30000, function() {
req.abort();
});
req.on("error", errorHandler);
req.end();
};
}
export function doDownloadLanguages() {
return function(dispatch, getState) {
if (!fs.existsSync(app.i18n.directory)) {
fs.mkdirSync(app.i18n.directory);
}
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
try {
const files = JSON.parse(xhr.responseText);
const actions = [];
files.forEach(file => {
actions.push(doDownloadLanguage(file));
});
dispatch(batchActions(...actions));
} catch (err) {
throw err;
}
} else {
throw new Error(
__("The list of available languages could not be retrieved.")
);
}
}
};
xhr.open("get", "http://i18n.lbry.io");
xhr.send();
};
}
export function doChangeLanguage(language) {
return function(dispatch, getState) {
lbry.setClientSetting(settings.LANGUAGE, language);
app.i18n.setLocale(language);
}; };
} }

View file

@ -62,7 +62,8 @@ class FileCard extends React.PureComponent {
? metadata.thumbnail ? metadata.thumbnail
: null; : 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); const isRewardContent =
claim && rewardedContentClaimIds.includes(claim.claim_id);
let description = ""; let description = "";
if (isResolvingUri && !claim) { if (isResolvingUri && !claim) {
@ -95,8 +96,9 @@ class FileCard extends React.PureComponent {
<div className="card__subtitle"> <div className="card__subtitle">
<span style={{ float: "right" }}> <span style={{ float: "right" }}>
<FilePrice uri={uri} /> <FilePrice uri={uri} />
{isRewardContent && <span>{" "}<IconFeatured /></span> } {isRewardContent && <span>{" "}<IconFeatured /></span>}
{fileInfo && <span>{" "}<Icon fixed icon="icon-folder" /></span> } {fileInfo &&
<span>{" "}<Icon fixed icon="icon-folder" /></span>}
</span> </span>
<UriIndicator uri={uri} /> <UriIndicator uri={uri} />
</div> </div>

View file

@ -71,7 +71,8 @@ class FileTile extends React.PureComponent {
? metadata.thumbnail ? metadata.thumbnail
: null; : 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); const isRewardContent =
claim && rewardedContentClaimIds.includes(claim.claim_id);
let onClick = () => navigate("/show", { uri }); let onClick = () => navigate("/show", { uri });
@ -109,7 +110,7 @@ class FileTile extends React.PureComponent {
<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 /> } {isRewardContent && <IconFeatured />}
<div className="meta">{uri}</div> <div className="meta">{uri}</div>
<h3> <h3>
<TruncatedText lines={1}>{title}</TruncatedText> <TruncatedText lines={1}>{title}</TruncatedText>

View file

@ -3,10 +3,11 @@ import { Icon } from "component/common.js";
const IconFeatured = props => { const IconFeatured = props => {
return ( return (
<span className="icon-featured" title={ __("Watch content with this icon to earn weekly rewards.")}> <span
<Icon icon="icon-rocket" className="icon-featured"
fixed title={__("Watch content with this icon to earn weekly rewards.")}
className="card__icon-featured-content" /> >
<Icon icon="icon-rocket" fixed className="card__icon-featured-content" />
</span> </span>
); );
}; };

View file

@ -21,7 +21,7 @@ const select = (state, props) => {
}; };
const perform = dispatch => ({ const perform = dispatch => ({
navigate: (uri) => dispatch(doNavigate(uri)), navigate: uri => dispatch(doNavigate(uri)),
verifyUserIdentity: token => dispatch(doUserIdentityVerify(token)), verifyUserIdentity: token => dispatch(doUserIdentityVerify(token)),
}); });

View file

@ -29,20 +29,34 @@ class UserVerify extends React.PureComponent {
<p> <p>
{__( {__(
"To ensure you are a real person, we require a valid credit or debit card." "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")} /> " " +
__("There is no charge at all, now or in the future.") +
" "}
<Link
href="https://lbry.io/faq/identity-requirements"
label={__("Read More")}
/>
</p> </p>
{errorMessage && <p className="form-field__error">{errorMessage}</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> <p>
{__("You can continue without this step, but you will not be eligible to earn rewards.")} <CardVerify
label={__("Link Card and Finish")}
disabled={isPending}
token={this.onToken.bind(this)}
stripeKey="pk_live_e8M4dRNnCCbmpZzduEUZBgJO"
/>
</p> </p>
<Link onClick={() => navigate("/discover")} button="alt" label={__("Skip Rewards")} /> <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> </div>
); );
} }

View file

@ -16,7 +16,11 @@ class Video extends React.PureComponent {
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
// reset playing state upon change path action // reset playing state upon change path action
if (!this.isMediaSame(nextProps) && this.props.fileInfo && this.state.isPlaying) { if (
!this.isMediaSame(nextProps) &&
this.props.fileInfo &&
this.state.isPlaying
) {
this.state.isPlaying = false; this.state.isPlaying = false;
} }
} }

View file

@ -113,3 +113,7 @@ 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"; export const FETCH_REWARD_CONTENT_COMPLETED = "FETCH_REWARD_CONTENT_COMPLETED";
//Language
export const DOWNLOAD_LANGUAGE_SUCCEEDED = "DOWNLOAD_LANGUAGE_SUCCEEDED";
export const DOWNLOAD_LANGUAGE_FAILED = "DOWNLOAD_LANGUAGE_FAILED";

View file

@ -0,0 +1,187 @@
const LANGUAGES = {
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"],
};
export default LANGUAGES;

View file

@ -27,7 +27,7 @@ jsonrpc.call = function(
xhr.addEventListener("load", function() { xhr.addEventListener("load", function() {
var response = JSON.parse(xhr.responseText); var response = JSON.parse(xhr.responseText);
let error = response.error || response.result && response.result.error; let error = response.error || (response.result && response.result.error);
if (error) { if (error) {
if (errorCallback) { if (errorCallback) {
errorCallback(error); errorCallback(error);

View file

@ -7,6 +7,7 @@ import { Provider } from "react-redux";
import store from "store.js"; import store from "store.js";
import SplashScreen from "component/splash"; import SplashScreen from "component/splash";
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 * as types from "constants/action_types"; import * as types from "constants/action_types";
@ -96,6 +97,8 @@ const updateProgress = () => {
const initialState = app.store.getState(); const initialState = app.store.getState();
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());

View file

@ -20,17 +20,22 @@ class BackupPage extends React.PureComponent {
<SubHeader /> <SubHeader />
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">
<h3>{__("Backup Wallet")}</h3> <h3>{__("Backup Your LBRY Credits")}</h3>
</div> </div>
<div className="card__content"> <div className="card__content">
<p> <p>
{__( {__(
"Currently, there is no automatic wallet backup, but it is fairly easy to back up manually." "Your LBRY credits are controllable by you and only you, via wallet file(s) stored locally on your computer."
)} )}
</p> </p>
<p> <p>
{__( {__(
"To backup your wallet, make a copy of the folder listed below:" "Currently, there is no automatic wallet backup. If you lose access to these files, you will lose your credits permanently."
)}
</p>
<p>
{__(
"However, it is fairly easy to back up manually. To backup your wallet, make a copy of the folder listed below:"
)} )}
</p> </p>
<p> <p>
@ -45,6 +50,13 @@ class BackupPage extends React.PureComponent {
)} )}
</strong> </strong>
</p> </p>
<p>
For more details on backing up and best practices,{" "}
<Link
href="https://lbry.io/faq/how-to-backup-wallet"
label={__("see this article")}
/>.
</p>
</div> </div>
</section> </section>
</main> </main>

View file

@ -111,7 +111,7 @@ class FilePage extends React.PureComponent {
{!fileInfo || fileInfo.written_bytes <= 0 {!fileInfo || fileInfo.written_bytes <= 0
? <span style={{ float: "right" }}> ? <span style={{ float: "right" }}>
<FilePrice uri={lbryuri.normalize(uri)} /> <FilePrice uri={lbryuri.normalize(uri)} />
{isRewardContent && <span>{" "}<IconFeatured /></span> } {isRewardContent && <span>{" "}<IconFeatured /></span>}
</span> </span>
: null} : null}
<h1>{title}</h1> <h1>{title}</h1>

View file

@ -127,7 +127,9 @@ class RewardsPage extends React.PureComponent {
<div> <div>
<div className="card__content empty"> <div className="card__content empty">
<p> <p>
{__("This application is unable to earn rewards due to an authentication failure.")} {__(
"This application is unable to earn rewards due to an authentication failure."
)}
</p> </p>
</div> </div>
</div> </div>

View file

@ -6,13 +6,21 @@ import {
doSetClientSetting, doSetClientSetting,
doGetThemes, doGetThemes,
doSetTheme, doSetTheme,
doChangeLanguage,
} from "actions/settings"; } from "actions/settings";
import { selectDaemonSettings, selectShowNsfw } from "selectors/settings"; import {
selectDaemonSettings,
selectShowNsfw,
selectLanguages,
} from "selectors/settings";
import { selectCurrentLanguage } from "selectors/app";
import SettingsPage from "./view"; import SettingsPage from "./view";
const select = state => ({ const select = state => ({
daemonSettings: selectDaemonSettings(state), daemonSettings: selectDaemonSettings(state),
showNsfw: selectShowNsfw(state), showNsfw: selectShowNsfw(state),
language: selectCurrentLanguage(state),
languages: selectLanguages(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
@ -21,6 +29,7 @@ const perform = dispatch => ({
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)), setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
setTheme: name => dispatch(doSetTheme(name)), setTheme: name => dispatch(doSetTheme(name)),
getThemes: () => dispatch(doGetThemes), getThemes: () => dispatch(doGetThemes),
changeLanguage: newLanguage => dispatch(doChangeLanguage(newLanguage)),
}); });
export default connect(select, perform)(SettingsPage); export default connect(select, perform)(SettingsPage);

View file

@ -110,11 +110,10 @@ class SettingsPage extends React.PureComponent {
this.props.setClientSetting(settings.SHOW_NSFW, event.target.checked); this.props.setClientSetting(settings.SHOW_NSFW, event.target.checked);
} }
// onLanguageChange(language) { onLanguageChange(e) {
// lbry.setClientSetting('language', language); this.props.changeLanguage(e.target.value);
// i18n.setLocale(language); this.forceUpdate();
// this.setState({language: language}) }
// }
onShowUnavailableChange(event) {} onShowUnavailableChange(event) {}
@ -125,7 +124,7 @@ class SettingsPage extends React.PureComponent {
componentDidMount() {} componentDidMount() {}
render() { render() {
const { daemonSettings } = this.props; const { daemonSettings, language, languages } = this.props;
if (!daemonSettings || Object.keys(daemonSettings).length === 0) { if (!daemonSettings || Object.keys(daemonSettings).length === 0) {
return ( return (
@ -137,6 +136,28 @@ class SettingsPage extends React.PureComponent {
return ( return (
<main className="main--single-column"> <main className="main--single-column">
<SubHeader /> <SubHeader />
<section className="card">
<div className="card__content">
<h3>{__("Language")}</h3>
</div>
<div className="card__content">
<div className="form-row">
<FormField
type="select"
name="language"
defaultValue={language}
onChange={this.onLanguageChange.bind(this)}
>
<option value="en">{__("English")}</option>
{Object.keys(languages).map(dLang =>
<option key={dLang} value={dLang}>
{languages[dLang]}
</option>
)}
</FormField>
</div>
</div>
</section>
<section className="card"> <section className="card">
<div className="card__content"> <div className="card__content">
<h3>{__("Download Directory")}</h3> <h3>{__("Download Directory")}</h3>

View file

@ -21,10 +21,7 @@ reducers[types.FETCH_FEATURED_CONTENT_COMPLETED] = function(state, action) {
}); });
}; };
reducers[types.FETCH_REWARD_CONTENT_COMPLETED] = function( reducers[types.FETCH_REWARD_CONTENT_COMPLETED] = function(state, action) {
state,
action
) {
const { claimIds, success } = action.data; const { claimIds, success } = action.data;
return Object.assign({}, state, { return Object.assign({}, state, {

View file

@ -1,5 +1,6 @@
import * as types from "constants/action_types"; import * as types from "constants/action_types";
import * as settings from "constants/settings"; import * as settings from "constants/settings";
import LANGUAGES from "constants/languages";
import lbry from "lbry"; import lbry from "lbry";
const reducers = {}; const reducers = {};
@ -8,7 +9,9 @@ const defaultState = {
showNsfw: lbry.getClientSetting("showNsfw"), showNsfw: lbry.getClientSetting("showNsfw"),
theme: lbry.getClientSetting(settings.THEME), theme: lbry.getClientSetting(settings.THEME),
themes: lbry.getClientSetting(settings.THEMES), themes: lbry.getClientSetting(settings.THEMES),
language: lbry.getClientSetting("language"),
}, },
languages: {},
}; };
reducers[types.DAEMON_SETTINGS_RECEIVED] = function(state, action) { reducers[types.DAEMON_SETTINGS_RECEIVED] = function(state, action) {
@ -28,6 +31,28 @@ reducers[types.CLIENT_SETTING_CHANGED] = function(state, action) {
}); });
}; };
reducers[types.DOWNLOAD_LANGUAGE_SUCCEEDED] = function(state, action) {
const languages = Object.assign({}, state.languages);
const language = action.data.language;
const langCode = language.substring(0, 2);
if (LANGUAGES[langCode]) {
languages[language] =
LANGUAGES[langCode][0] + " (" + LANGUAGES[langCode][1] + ")";
} else {
languages[langCode] = langCode;
}
return Object.assign({}, state, { languages });
};
reducers[types.DOWNLOAD_LANGUAGE_FAILED] = function(state, action) {
const languages = Object.assign({}, state.languages);
delete languages[action.data.language];
return Object.assign({}, state, { languages });
};
export default function reducer(state = defaultState, action) { export default function reducer(state = defaultState, action) {
const handler = reducers[action.type]; const handler = reducers[action.type];
if (handler) return handler(state, action); if (handler) return handler(state, action);

View file

@ -218,6 +218,11 @@ export const selectBadgeNumber = createSelector(
state => state.badgeNumber state => state.badgeNumber
); );
export const selectCurrentLanguage = createSelector(
_selectState,
() => app.i18n.getLocale() || "en"
);
export const selectPathAfterAuth = createSelector( export const selectPathAfterAuth = createSelector(
_selectState, _selectState,
state => state.pathAfterAuth state => state.pathAfterAuth

View file

@ -21,3 +21,8 @@ export const selectShowNsfw = createSelector(
selectClientSettings, selectClientSettings,
clientSettings => !!clientSettings.showNsfw clientSettings => !!clientSettings.showNsfw
); );
export const selectLanguages = createSelector(
_selectState,
state => state.languages || {}
);

View file

@ -1,5 +1,3 @@
const { remote } = require("electron");
/** /**
* Thin wrapper around localStorage.getItem(). Parses JSON and returns undefined if the value * Thin wrapper around localStorage.getItem(). Parses JSON and returns undefined if the value
* is not set yet. * is not set yet.