rename lbrytv to web

language and API consts

improve customization
custom homepages
get config from .env.default
custom title and logo

small changes

add pinned item to sidebar

rebase?
This commit is contained in:
jessop 2020-05-07 14:44:11 -04:00
parent 1dba194b67
commit e3c2919373
54 changed files with 774 additions and 301 deletions

12
.env.defaults Normal file
View file

@ -0,0 +1,12 @@
# Copy this file to .env to make modifications
WEBPACK_WEB_PORT=9090
WEBPACK_ELECTRON_PORT=9091
WEB_SERVER_PORT=1337
DOMAIN=lbry.tv
URL=https://lbry.tv
SITE_TITLE=lbry.tv
LOGO_TITLE=LBRY
LBRY_WEB_API=https://api.lbry.tv
LBRY_WEB_STREAMING_API=https://cdn.lbryplayer.xyz
WELCOME_VERSION=1.0
DEFAULT_LANGUAGE=en

View file

@ -27,8 +27,9 @@ module.name_mapper='^analytics\(.*\)$' -> '<PROJECT_ROOT>/ui/analytics\1'
module.name_mapper='^i18n\(.*\)$' -> '<PROJECT_ROOT>/ui/i18n\1' module.name_mapper='^i18n\(.*\)$' -> '<PROJECT_ROOT>/ui/i18n\1'
module.name_mapper='^effects\(.*\)$' -> '<PROJECT_ROOT>/ui/effects\1' module.name_mapper='^effects\(.*\)$' -> '<PROJECT_ROOT>/ui/effects\1'
module.name_mapper='^config\(.*\)$' -> '<PROJECT_ROOT>/config\1' module.name_mapper='^config\(.*\)$' -> '<PROJECT_ROOT>/config\1'
module.name_mapper='^lbrytv\/component\(.*\)$' -> '<PROJECT_ROOT>/lbrytv/component\1' module.name_mapper='^web\/component\(.*\)$' -> '<PROJECT_ROOT>/web/component\1'
module.name_mapper='^lbrytv\/effects\(.*\)$' -> '<PROJECT_ROOT>/lbrytv/effects\1' module.name_mapper='^web\/effects\(.*\)$' -> '<PROJECT_ROOT>/web/effects\1'
module.name_mapper='^lbrytv\/page\(.*\)$' -> '<PROJECT_ROOT>/lbrytv/page\1' module.name_mapper='^web\/page\(.*\)$' -> '<PROJECT_ROOT>/web/page\1'
module.name_mapper='^homepage\(.*\)$' -> '<PROJECT_ROOT>/ui/util/homepage\1'
[strict] [strict]

8
.gitignore vendored
View file

@ -13,5 +13,9 @@ package-lock.json
.transifexrc .transifexrc
.idea/ .idea/
/build/daemon* /build/daemon*
/lbrytv/dist/ /web/dist/*
/lbrytv/node_modules /web/node_modules/*
/web/.env
/custom/*
!/custom/homepage.example.js
.env

View file

@ -1,9 +1,9 @@
{ {
"linters": { "linters": {
"ui/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"], "ui/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"],
"lbrytv/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"], "web/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"],
"ui/**/*.{js,jsx}": ["eslint", "flow focus-check --color always", "git add"], "ui/**/*.{js,jsx}": ["eslint", "flow focus-check --color always", "git add"],
"lbrytv/**/*.{js,jsx,scss}": ["eslint", "git add"] "web/**/*.{js,jsx,scss}": ["eslint", "git add"]
}, },
"ignore": ["node_modules", "dist", "package-lock.json"] "ignore": ["node_modules", "web/dist/**/*", "dist/**/*", "package-lock.json"]
} }

View file

@ -92,7 +92,7 @@ You can run the web version (lbry.tv), the electron app, or both at the same tim
- If you want to build and launch the production app you can run `yarn build`. This will give you an executable inside the `/dist` folder. We use [electron-builder](https://github.com/electron-userland/electron-builder) to create distributable packages. - If you want to build and launch the production app you can run `yarn build`. This will give you an executable inside the `/dist` folder. We use [electron-builder](https://github.com/electron-userland/electron-builder) to create distributable packages.
#### Run the web app #### Run the web app for development
`yarn compile:web` (this is only needed the first time you run the app) `yarn compile:web` (this is only needed the first time you run the app)
@ -100,6 +100,29 @@ You can run the web version (lbry.tv), the electron app, or both at the same tim
- This uses webpack-dev-server and includes hot-reloading. If you want to debug the [web server we use in production](https://github.com/lbryio/lbry-desktop/blob/master/src/platforms/web/server.js) you can run `yarn dev:web-server`. This starts a server at `localhost:1337` and does not include hot reloading. - This uses webpack-dev-server and includes hot-reloading. If you want to debug the [web server we use in production](https://github.com/lbryio/lbry-desktop/blob/master/src/platforms/web/server.js) you can run `yarn dev:web-server`. This starts a server at `localhost:1337` and does not include hot reloading.
#### Customize the web app
- Enter web directory, copy .env.defaults to .env and make changes
```
cd web
cp .env.defaults .env
nano .env
```
- If you want to customize the homepage content
1. add `CUSTOM_HOMEPAGE=true` to the 'web/.env' file
2. copy `/custom/homepage.example.js` to `/custom/homepage.js` and make desired changes to `homepage.js`
#### Deploy the web app (*experimental*)
1. Create a server with a domain name and a reverse proxy https to port 1337.
2. Install pm2, node v10, yarn
3. Clone this repo
4. Make any customizations as above
5. Run 'yarn' to install
6. Run yarn compile:web to build
7. Set up pm2 to start ./web/index.js
#### Run both at the same time #### Run both at the same time
Run the two commands above in separate terminal windows Run the two commands above in separate terminal windows

View file

@ -1,13 +1,22 @@
// On Web, this will find .env.defaults and optional .env in web/
// On Desktop App, this will find .env.defaults and optional .env in root dir
require('dotenv-defaults').config({silent: false});
const config = { const config = {
WEBPACK_WEB_PORT: 9090, WEBPACK_WEB_PORT: process.env.WEBPACK_WEB_PORT,
WEBPACK_ELECTRON_PORT: 9091, WEBPACK_ELECTRON_PORT: process.env.WEBPACK_ELECTRON_PORT,
WEB_SERVER_PORT: 1337, WEB_SERVER_PORT: process.env.WEB_SERVER_PORT,
DOMAIN: 'lbry.tv', DOMAIN: process.env.DOMAIN,
URL: 'https://lbry.tv', URL: process.env.URL, //lbry.tv',
SITE_TITLE: 'lbry.tv', SITE_TITLE: process.env.SITE_TITLE,
LBRY_TV_API: 'https://api.lbry.tv', LBRY_WEB_API: process.env.LBRY_WEB_API, //api.lbry.tv',
LBRY_TV_STREAMING_API: 'https://cdn.lbryplayer.xyz', LBRY_WEB_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, //cdn.lbryplayer.xyz',
WELCOME_VERSION: 1.0, WELCOME_VERSION: process.env.WELCOME_VERSION,
DEFAULT_LANGUAGE: process.env.DEFAULT_LANGUAGE,
LOGO_TITLE: process.env.LOGO_TITLE,
PINNED_URI_1: process.env.PINNED_URI_1,
PINNED_LABEL_1: process.env.PINNED_LABEL_1,
PINNED_URI_2: process.env.PINNED_URI_2,
PINNED_LABEL_2: process.env.PINNED_LABEL_2,
}; };
config.URL_LOCAL = `http://localhost:${config.WEB_SERVER_PORT}`; config.URL_LOCAL = `http://localhost:${config.WEB_SERVER_PORT}`;

306
custom/homepage.example.js Normal file
View file

@ -0,0 +1,306 @@
// @flow
/*
Creating custom homepage items
1) in web/ folder, copy `.env.defaults` to `.env` and make CUSTOM_HOMEPAGE=true
2) copy this file to homepage.js
3) Create item objects
{
title, // how the title link displays
link, // where the title link goes
options // what search results populate the homepage item
}
4) rowData.push(item) for each item in the order you want them
Item object examples:
Specific Channel
const LATEST_FROM_%CHANNEL% = {
title: 'Latest From @%channel%',
link: `/@%channelName%:%canonicalId%`,
options: {
orderBy: ['release_time'],
pageSize: 4,
channelIds: ['%channelId%'],
},
};
More complex List of channels with description icon filtering by fee_amount etc are possible
SOME_CHANNEL_IDS = ['channelId1', 'channelId2']
SOME_TAG = '
const SOME_CHANNELS_ITEM = {
title: '#aTitle',
link: `/$/${PAGES.DISCOVER}?${CS.TAGS_KEY}=aTag&fee_amount=>0&${CS.CLAIM_TYPE}=${CS.CLAIM_STREAM}&${
CS.CHANNEL_IDS_KEY
}=${SOME_CHANNEL_IDS.join(',')}`,
help: (
<div className="claim-grid__help">
<Icon
icon={ICONS.HELP}
tooltip
customTooltipText={__(
'Some tooltip text.'
)}
/>
</div>
),
options: { // search options
feeAmount: '>0',
claimType: ['stream'],
tags: ['tagOne', 'tagTwo'],
pageSize: 8,
channelIds: SOME_CHANNEL_IDS,
},
};
*/
import * as PAGES from 'constants/pages';
import * as CS from 'constants/claim_search';
import * as ICONS from 'constants/icons';
import { parseURI } from 'lbry-redux';
import moment from 'moment';
import { toCapitalCase } from 'util/string';
import React from 'react';
import Icon from 'component/common/icon';
type RowDataItem = {
title: string,
link?: string,
help?: any,
options?: {},
};
export default function getHomePageRowData(
authenticated,
showPersonalizedChannels: boolean,
showPersonalizedTags: boolean,
subscribedChannels: Array<Subscription>,
followedTags: Array<Tag>,
showIndividualTags: boolean
) {
let rowData: Array<RowDataItem> = [];
const individualTagDataItems: Array<RowDataItem> = [];
const PAID_BETA_CHANNEL_IDS_KEY = [
'4ee7cfaf1fc50a6df858ed0b99c278d633bccca9',
'5af39f818f668d8c00943c9326c5201c4fe3c423',
'cda9c4e92f19d6fe0764524a2012056e06ca2055',
'760da3ba3dd85830a843beaaed543a89b7a367e7',
'40c36948f0da072dcba3e4833e90f71e16de78be',
'e8f68563d242f6ac9784dcbc41dd86c28a9391d6',
'7236fc5d2783ea7314d9076ae6c8a250e3992d1a',
'8627af93c1a1219150f06b698f4b33e6ed2f1c1e',
'c5b0b17838df2f6c31162f64d55f60f34ae8bfc6',
'f576d5dba905fc179de880c3fe3eb3281ea74f59',
'97dd77c93c9603cbb2583f3589f7f5a6c92baa43',
'f399d873e0c37cf24de9569b5f22bbb30a5c6709',
'dba870d0620d41b2b9a152c961e0c06cf875ccfc',
'ca1fd651c9d14bf2e5088bb2aa0146ee7aeb2ae0',
'50ad846a4b1543b847bf3fdafb7b45f6b2f5844c',
'e09ff5abe9fb44dd0dd0576894a6db60a6211603',
'7b6f7517f6b816827d076fa0eaad550aa315a4e7',
'2068452c41d8da3bd68961335da0072a99258a1a',
'3645cf2f5d0bdac0523f945be1c3ff60758f7845',
'4da85b12244839d6368b9290f1619ff9514ab2a8',
'4ad942982e43326c7700b1b6443049b3cfd82161',
'55304f219244abf82f684f759cc0c7769242f3b4',
'8f42e5b592bb7f7a03f4a94a86a41b1236bb099f',
'e2a014d885a48f5be2dc6409610996337312facb',
'c18996ca488753f714d36d4654715927c1d7f9c2',
'ebc4214424cfa683a7046e1f794fea1e44788d84',
'06b6d6d6a893fb589ec2ded948f5122856921ed5',
'07e4546674268fc0222b2ca22d31d0549dc217ee',
'060940e41973d4f7f16d72a2733138e931c35f41',
'f8d6eccd887c9cebd36b1d42aa349279b7f5c3ed',
'68098b8426f967b8d04cc566348b5c128823219e',
'2bfe6cdb24a21bdc1b76fb7c416edd50e9e85945',
'1f9bb08bfa2259629f4aaa9ed40f97e9a41b6fa1',
'2f20148495612946675fe1c8ea99171e4d950b81',
'bc6938fa1e09e840056c2e831abf9664f397c472',
'2a6194792beac5130641e932b5ac6e5a99b5ca4f',
'185ba2bd547a5e4a77d29fe6c1484f47db5e058f',
'29cc7f6081268eaa5b3f2946e0cd0b952a94812c',
'ffdc62ac2f7549398d3aca9d2119e83d80d588d5',
'd7a4d2808074b0c55d6b239f69d90e7a4930f943',
'd58aa4a0b2f6c2504c3abce8de3f1afb71800acc',
'77ae23dc7eb8a75609881d4548a79e4935a89d37',
'f79bce8a60fbece671f6265adc39f6469f3b9b8c',
'051995fdf0af634e4911704057a551e9392e62b1',
'b0e489f986c345aef23c4a48d91cbcf5a6fdb9ac',
'825aa21c8c0bda4ded3e69a69238763c8cfcc13b',
'49389450b1241f5d8f4c8c4271a3eb56bba33965',
'f3b9973e1725ecb50da3e6fa4d47343c98ef0382',
'321b33d22c8e24ef207e3f357a4573f6a56611f3',
'20d694ada07e740c6fa43a8c324cb7d6e362b5ee',
'cf7792c2a37d0d76aaaff84aff0b99a8c791429d',
'8316ac90764fedf3147799b7b81a6575a9cc398e',
'8972a1bd06de5186e5e89292b05aac8aaa817791',
'5da63df97c8255ae94a88940695b8471657dd5a1',
'f3da2196b5151570d980b34d311ee0973225a68e',
'7644fdb8342624f6c647c79de25610801573fa68',
];
const TV_PAID_BETA_ROW = {
title: '#lbrytvpaidbeta',
link: `/$/${PAGES.DISCOVER}?${CS.TAGS_KEY}=lbrytvpaidbeta&fee_amount=>0&${CS.CLAIM_TYPE}=${CS.CLAIM_STREAM}&${
CS.CHANNEL_IDS_KEY
}=${PAID_BETA_CHANNEL_IDS_KEY.join(',')}`,
help: (
<div className="claim-grid__help">
<Icon
icon={ICONS.HELP}
tooltip
customTooltipText={__(
'Check your rewards page to see if you qualify for paid content reimbursement. Only content in this section qualifies.'
)}
/>
</div>
),
options: {
feeAmount: '>0',
claimType: ['stream'],
tags: ['lbrytvpaidbeta', 'lbry tvpaidbeta'],
pageSize: 8,
channelIds: PAID_BETA_CHANNEL_IDS_KEY,
},
};
if (followedTags.length) {
followedTags.forEach((tag: Tag) => {
individualTagDataItems.push({
title: `Trending for #${toCapitalCase(tag.name)}`,
link: `/$/${PAGES.DISCOVER}?t=${tag.name}`,
options: {
pageSize: 4,
tags: [tag.name],
claimType: ['stream'],
},
});
});
}
const RECENT_FROM_FOLLOWING = {
title: 'Recent From Following',
link: `/$/${PAGES.CHANNELS_FOLLOWING}`,
options: {
orderBy: ['release_time'],
releaseTime:
subscribedChannels.length > 20
? `>${Math.floor(
moment()
.subtract(6, 'months')
.startOf('week')
.unix()
)}`
: `>${Math.floor(
moment()
.subtract(1, 'year')
.startOf('week')
.unix()
)}`,
pageSize: subscribedChannels.length > 3 ? 8 : 4,
channelIds: subscribedChannels.map((subscription: Subscription) => {
const { channelClaimId } = parseURI(subscription.uri);
return channelClaimId;
}),
},
};
const TOP_CONTENT_TODAY = {
title: 'Top Content from Today',
link: `/$/${PAGES.DISCOVER}?${CS.ORDER_BY_KEY}=${CS.ORDER_BY_TOP}&${CS.FRESH_KEY}=${CS.FRESH_DAY}`,
options: {
pageSize: showPersonalizedChannels || showPersonalizedTags ? 4 : 8,
orderBy: ['effective_amount'],
claimType: ['stream'],
releaseTime: `>${Math.floor(
moment()
.subtract(1, 'day')
.startOf('day')
.unix()
)}`,
},
};
const TOP_CHANNELS = {
title: 'Top Channels On LBRY',
link: `/$/${PAGES.DISCOVER}?claim_type=channel&${CS.ORDER_BY_KEY}=${CS.ORDER_BY_TOP}&${CS.FRESH_KEY}=${CS.FRESH_ALL}`,
options: {
orderBy: ['effective_amount'],
claimType: ['channel'],
},
};
const TRENDING_CLASSICS = {
title: 'Trending Classics',
link: `/$/${PAGES.DISCOVER}?${CS.ORDER_BY_KEY}=${CS.ORDER_BY_TRENDING}&${CS.FRESH_KEY}=${CS.FRESH_WEEK}`,
options: {
pageSize: 4,
claimType: ['stream'],
releaseTime: `<${Math.floor(
moment()
.subtract(6, 'month')
.startOf('day')
.unix()
)}`,
},
};
const TRENDING_ON_LBRY = {
title: 'Trending On LBRY',
link: `/$/${PAGES.DISCOVER}`,
options: {
pageSize: showPersonalizedChannels || showPersonalizedTags ? 4 : 8,
},
};
const TRENDING_FOR_TAGS = {
title: 'Trending For Your Tags',
link: `/$/${PAGES.TAGS_FOLLOWING}`,
options: {
tags: followedTags.map(tag => tag.name),
claimType: ['stream'],
},
};
const LATEST_FROM_LBRY = {
title: 'Latest From @lbry',
link: `/@lbry:3f`,
options: {
orderBy: ['release_time'],
pageSize: 4,
channelIds: ['3fda836a92faaceedfe398225fb9b2ee2ed1f01a'],
},
};
const LATEST_FROM_LBRYCAST = {
title: 'Latest From @lbrycast',
link: `/@lbrycast:4`,
options: {
orderBy: ['release_time'],
pageSize: 4,
channelIds: ['3fda836a92faaceedfe398225fb9b2ee2ed1f01a'],
},
};
if (showPersonalizedChannels) rowData.push(RECENT_FROM_FOLLOWING);
if (showPersonalizedTags && !showIndividualTags) rowData.push(TRENDING_FOR_TAGS);
if (showPersonalizedTags && showIndividualTags) {
individualTagDataItems.forEach((item: RowDataItem) => {
rowData.push(item);
});
}
if (authenticated) {
rowData.push(TV_PAID_BETA_ROW);
}
rowData.push(TOP_CONTENT_TODAY);
rowData.push(TRENDING_ON_LBRY);
if (!showPersonalizedChannels) rowData.push(TOP_CHANNELS);
if (!authenticated) {
rowData.push(TV_PAID_BETA_ROW);
}
rowData.push(TRENDING_CLASSICS);
rowData.push(LATEST_FROM_LBRYCAST);
rowData.push(LATEST_FROM_LBRY);
return rowData;
}

View file

@ -21,16 +21,15 @@
"main": "./dist/electron/main.js", "main": "./dist/electron/main.js",
"scripts": { "scripts": {
"compile:electron": "node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --config webpack.electron.config.js", "compile:electron": "node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --config webpack.electron.config.js",
"compile:web": "cd ./lbrytv && node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --config webpack.config.js", "compile:web": "cd web && node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --config webpack.config.js",
"compile": "cross-env NODE_ENV=production yarn compile:electron && cross-env NODE_ENV=production yarn compile:web", "compile": "cross-env NODE_ENV=production yarn compile:electron && cross-env NODE_ENV=production yarn compile:web",
"dev": "yarn dev:electron", "dev": "yarn dev:electron",
"dev:electron": "cross-env NODE_ENV=development node ./electron/devServer.js", "dev:electron": "cross-env NODE_ENV=development node ./electron/devServer.js",
"dev:web": "cd ./lbrytv && yarn dev", "dev:web": "cd web && yarn dev",
"dev:web-server": "cross-env NODE_ENV=development yarn compile:web && concurrently \"cross-env NODE_ENV=development yarn compile:web --watch\" \"cd ./lbrytv && yarn dev:server\"", "dev:web-server": "cross-env NODE_ENV=development yarn compile:web && concurrently \"cross-env NODE_ENV=development yarn compile:web --watch\" \"cd web && yarn dev:server\"",
"dev:internal-apis": "LBRY_API_URL='http://localhost:8080' yarn dev:electron", "dev:internal-apis": "LBRY_API_URL='http://localhost:8080' yarn dev:electron",
"dev:iatv": "LBRY_API_URL='http://localhost:15400' SDK_API_URL='http://localhost:15100' yarn dev:web", "dev:iatv": "LBRY_API_URL='http://localhost:15400' SDK_API_URL='http://localhost:15100' yarn dev:web",
"run:web": "cross-env NODE_ENV=production yarn compile:web && node ./dist/web/server.js", "run:web-server": "cross-env NODE_ENV=production yarn compile:web && cd web && yarn dev:server",
"run:web-server": "cross-env NODE_ENV=production yarn compile:web && cd ./lbrytv && yarn dev:server",
"pack": "electron-builder --dir", "pack": "electron-builder --dir",
"dist": "electron-builder", "dist": "electron-builder",
"build": "cross-env NODE_ENV=production yarn compile:electron && electron-builder build", "build": "cross-env NODE_ENV=production yarn compile:electron && electron-builder build",
@ -40,7 +39,7 @@
"flow-defs": "flow-typed install", "flow-defs": "flow-typed install",
"precommit": "lint-staged", "precommit": "lint-staged",
"preinstall": "yarn cache clean lbry-redux && yarn cache clean lbryinc", "preinstall": "yarn cache clean lbry-redux && yarn cache clean lbryinc",
"postinstall": "cd ./lbrytv && yarn && cd .. && if-env NODE_ENV=production && yarn postinstall:warning || if-env APP_ENV=web && echo 'Done installing deps' || yarn postinstall:electron", "postinstall": "cd web && yarn && cd .. && if-env NODE_ENV=production && yarn postinstall:warning || if-env APP_ENV=web && echo 'Done installing deps' || yarn postinstall:electron",
"postinstall:electron": "electron-builder install-app-deps && node ./build/downloadDaemon.js", "postinstall:electron": "electron-builder install-app-deps && node ./build/downloadDaemon.js",
"postinstall:warning": "echo '\n\nWARNING\n\nNot all node modules were installed because NODE_ENV is set to \"production\".\nThis should only be set after installing dependencies with \"yarn\". The app will not work.\n\n'" "postinstall:warning": "echo '\n\nWARNING\n\nNot all node modules were installed because NODE_ENV is set to \"production\".\nThis should only be set after installing dependencies with \"yarn\". The app will not work.\n\n'"
}, },
@ -102,6 +101,8 @@
"del": "^3.0.0", "del": "^3.0.0",
"devtron": "^1.4.0", "devtron": "^1.4.0",
"dom-scroll-into-view": "^1.2.1", "dom-scroll-into-view": "^1.2.1",
"dotenv-defaults": "^1.1.1",
"dotenv-webpack": "^1.8.0",
"electron": "4.1.0", "electron": "4.1.0",
"electron-builder": "^22.4.0", "electron-builder": "^22.4.0",
"electron-devtools-installer": "^2.2.4", "electron-devtools-installer": "^2.2.4",
@ -133,7 +134,7 @@
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"lbry-format": "https://github.com/lbryio/lbry-format.git", "lbry-format": "https://github.com/lbryio/lbry-format.git",
"lbry-redux": "lbryio/lbry-redux#d2079111b3372eb926d2753991bed860e57a970a", "lbry-redux": "lbryio/lbry-redux#d2079111b3372eb926d2753991bed860e57a970a",
"lbryinc": "lbryio/lbryinc#6a52f8026cdc7cd56d200fb5c46f852e0139bbeb", "lbryinc": "lbryio/lbryinc#8bdf1ac44d03cef9d798df92e3192a4591845f17",
"lint-staged": "^7.0.2", "lint-staged": "^7.0.2",
"localforage": "^1.7.1", "localforage": "^1.7.1",
"lodash-es": "^4.17.14", "lodash-es": "^4.17.14",

View file

@ -18,10 +18,10 @@ import { rewards as REWARDS } from 'lbryinc';
import usePersistedState from 'effects/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
import FileDrop from 'component/fileDrop'; import FileDrop from 'component/fileDrop';
// @if TARGET='web' // @if TARGET='web'
import OpenInAppLink from 'lbrytv/component/openInAppLink'; import OpenInAppLink from 'web/component/openInAppLink';
import YoutubeWelcome from 'lbrytv/component/youtubeReferralWelcome'; import YoutubeWelcome from 'web/component/youtubeReferralWelcome';
import NagDegradedPerformance from 'lbrytv/component/nag-degraded-performance'; import NagDegradedPerformance from 'web/component/nag-degraded-performance';
import NagDataCollection from 'lbrytv/component/nag-data-collection'; import NagDataCollection from 'web/component/nag-data-collection';
import { import {
useDegradedPerformance, useDegradedPerformance,
@ -29,7 +29,7 @@ import {
STATUS_DEGRADED, STATUS_DEGRADED,
STATUS_FAILING, STATUS_FAILING,
STATUS_DOWN, STATUS_DOWN,
} from 'lbrytv/effects/use-degraded-performance'; } from 'web/effects/use-degraded-performance';
// @endif // @endif
export const MAIN_WRAPPER_CLASS = 'main-wrapper'; export const MAIN_WRAPPER_CLASS = 'main-wrapper';
// @if TARGET='app' // @if TARGET='app'

View file

@ -5,7 +5,7 @@ import { withRouter } from 'react-router-dom';
import Button from 'component/button'; import Button from 'component/button';
import ClaimListDiscover from 'component/claimListDiscover'; import ClaimListDiscover from 'component/claimListDiscover';
import * as CS from 'constants/claim_search'; import * as CS from 'constants/claim_search';
import Ads from 'lbrytv/component/ads'; import Ads from 'web/component/ads';
type Props = { type Props = {
uri: string, uri: string,

View file

@ -629,4 +629,9 @@ export const icons = {
<polyline points="22 4 12 14.01 9 11.01" /> <polyline points="22 4 12 14.01 9 11.01" />
</g> </g>
), ),
[ICONS.PINNED]: buildIcon(
<g>
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" />
</g>
),
}; };

View file

@ -3,7 +3,7 @@ import * as ICONS from 'constants/icons';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { generateEmbedUrl } from 'util/lbrytv'; import { generateEmbedUrl } from 'util/web';
type Props = { type Props = {
copyable: string, copyable: string,

View file

@ -12,6 +12,7 @@ import Icon from 'component/common/icon';
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button'; import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
import Tooltip from 'component/common/tooltip'; import Tooltip from 'component/common/tooltip';
import NavigationButton from 'component/navigationButton'; import NavigationButton from 'component/navigationButton';
import { LOGO_TITLE } from 'config';
// @if TARGET='app' // @if TARGET='app'
import { remote } from 'electron'; import { remote } from 'electron';
import { IS_MAC } from 'component/app/view'; import { IS_MAC } from 'component/app/view';
@ -137,7 +138,7 @@ const Header = (props: Props) => {
<div className="header__navigation"> <div className="header__navigation">
<Button <Button
className="header__navigation-item header__navigation-item--lbry header__navigation-item--button-mobile" className="header__navigation-item header__navigation-item--lbry header__navigation-item--button-mobile"
label={__('LBRY')} label={LOGO_TITLE}
icon={ICONS.LBRY} icon={ICONS.LBRY}
onClick={() => { onClick={() => {
if (history.location.pathname === '/') window.location.reload(); if (history.location.pathname === '/') window.location.reload();

View file

@ -4,7 +4,7 @@ import React, { Fragment } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import SideNavigation from 'component/sideNavigation'; import SideNavigation from 'component/sideNavigation';
import Header from 'component/header'; import Header from 'component/header';
import Footer from 'lbrytv/component/footer'; import Footer from 'web/component/footer';
export const MAIN_CLASS = 'main'; export const MAIN_CLASS = 'main';
type Props = { type Props = {

View file

@ -1,7 +1,7 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import ClaimList from 'component/claimList'; import ClaimList from 'component/claimList';
import Ads from 'lbrytv/component/ads'; import Ads from 'web/component/ads';
import Card from 'component/common/card'; import Card from 'component/common/card';
type Options = { type Options = {

View file

@ -9,8 +9,9 @@ import StickyBox from 'react-sticky-box/dist/esnext';
import Spinner from 'component/spinner'; import Spinner from 'component/spinner';
import usePersistedState from 'effects/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
import classnames from 'classnames'; import classnames from 'classnames';
import { PINNED_LABEL_1, PINNED_URI_1, PINNED_URI_2, PINNED_LABEL_2 } from 'config';
// @if TARGET='web' // @if TARGET='web'
// import Ads from 'lbrytv/component/ads'; // import Ads from 'web/component/ads';
// @endif // @endif
const SHOW_CHANNELS = 'SHOW_CHANNELS'; const SHOW_CHANNELS = 'SHOW_CHANNELS';
@ -84,9 +85,9 @@ function SideNavigation(props: Props) {
} }
}, [setPulseLibrary, purchaseSuccess, doClearPurchasedUriSuccess]); }, [setPulseLibrary, purchaseSuccess, doClearPurchasedUriSuccess]);
function buildLink(path, label, icon, onClick, requiresAuth = false) { function buildLink(path, label, icon, onClick, requiresAuth = false, isLiteral = false) {
return { return {
navigate: path ? `$/${path}` : '/', navigate: !isLiteral ? `$/${path}` : `${path}`,
label, label,
icon, icon,
onClick, onClick,
@ -117,7 +118,7 @@ function SideNavigation(props: Props) {
: {}), : {}),
}, },
{ {
...buildLink(null, __('Home'), ICONS.HOME), ...buildLink('/', __('Home'), ICONS.HOME, null, null, true),
}, },
{ {
...buildLink( ...buildLink(
@ -137,6 +138,18 @@ function SideNavigation(props: Props) {
{ {
...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY), ...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY),
}, },
// @if TARGET='web'
{
...(PINNED_URI_1
? { ...buildLink(`${PINNED_URI_1}`, `${PINNED_LABEL_1}`, ICONS.PINNED, null, null, true) }
: {}),
},
{
...(PINNED_URI_2
? { ...buildLink(`${PINNED_URI_2}`, `${PINNED_LABEL_2}`, ICONS.PINNED, null, null, true) }
: {}),
},
// @endif
{ {
...(expanded ? { ...buildLink(PAGES.SETTINGS, __('Settings'), ICONS.SETTINGS) } : {}), ...(expanded ? { ...buildLink(PAGES.SETTINGS, __('Settings'), ICONS.SETTINGS) } : {}),
}, },

View file

@ -4,7 +4,7 @@ import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import CopyableText from 'component/copyableText'; import CopyableText from 'component/copyableText';
import EmbedTextArea from 'component/embedTextArea'; import EmbedTextArea from 'component/embedTextArea';
import { generateDownloadUrl } from 'util/lbrytv'; import { generateDownloadUrl } from 'util/web';
import useIsMobile from 'effects/use-is-mobile'; import useIsMobile from 'effects/use-is-mobile';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import { hmsToSeconds, secondsToHms } from 'util/time'; import { hmsToSeconds, secondsToHms } from 'util/time';

View file

@ -10,7 +10,7 @@ import classnames from 'classnames';
import { FORCE_CONTENT_TYPE_PLAYER } from 'constants/claim'; import { FORCE_CONTENT_TYPE_PLAYER } from 'constants/claim';
import AutoplayCountdown from 'component/autoplayCountdown'; import AutoplayCountdown from 'component/autoplayCountdown';
import usePrevious from 'effects/use-previous'; import usePrevious from 'effects/use-previous';
import FileViewerEmbeddedEnded from 'lbrytv/component/fileViewerEmbeddedEnded'; import FileViewerEmbeddedEnded from 'web/component/fileViewerEmbeddedEnded';
import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle'; import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle';
import LoadingScreen from 'component/common/loading-screen'; import LoadingScreen from 'component/common/loading-screen';

View file

@ -101,3 +101,4 @@ export const SCIENCE = 'Science';
export const ANALYTICS = 'BarChart2'; export const ANALYTICS = 'BarChart2';
export const PURCHASED = 'Key'; export const PURCHASED = 'Key';
export const CIRCLE = 'Circle'; export const CIRCLE = 'Circle';
export const PINNED = 'Pinned';

View file

@ -1,6 +1,6 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import { generateStreamUrl } from 'util/lbrytv'; import { generateStreamUrl } from 'util/web';
export default function useGetThumbnail( export default function useGetThumbnail(
uri: string, uri: string,

View file

@ -15,7 +15,7 @@ import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { doDaemonReady, doAutoUpdate, doOpenModal, doHideModal, doToggle3PAnalytics } from 'redux/actions/app'; import { doDaemonReady, doAutoUpdate, doOpenModal, doHideModal, doToggle3PAnalytics } from 'redux/actions/app';
import { Lbry, doToast, isURIValid, setSearchApi, apiCall } from 'lbry-redux'; import { Lbry, doToast, isURIValid, setSearchApi, apiCall } from 'lbry-redux';
import { doSetLanguage, doUpdateIsNightAsync } from 'redux/actions/settings'; import { doSetLanguage, doFetchLanguage, doUpdateIsNightAsync } from 'redux/actions/settings';
import { Lbryio, rewards, doBlackListedOutpointsSubscribe, doFilteredOutpointsSubscribe } from 'lbryinc'; import { Lbryio, rewards, doBlackListedOutpointsSubscribe, doFilteredOutpointsSubscribe } from 'lbryinc';
import { store, persistor, history } from 'store'; import { store, persistor, history } from 'store';
import app from './app'; import app from './app';
@ -31,7 +31,7 @@ import {
doAuthTokenRefresh, doAuthTokenRefresh,
} from 'util/saved-passwords'; } from 'util/saved-passwords';
import { X_LBRY_AUTH_TOKEN } from 'constants/token'; import { X_LBRY_AUTH_TOKEN } from 'constants/token';
import { LBRY_TV_API } from 'config'; import { LBRY_WEB_API } from 'config';
// Import our app styles // Import our app styles
// If a style is not necessary for the initial page load, it should be removed from `all.scss` // If a style is not necessary for the initial page load, it should be removed from `all.scss`
@ -39,8 +39,9 @@ import { LBRY_TV_API } from 'config';
import 'scss/all.scss'; import 'scss/all.scss';
// @if TARGET='web' // @if TARGET='web'
// These overrides can't live in lbrytv/ because they need to use the same instance of `Lbry` // These overrides can't live in web/ because they need to use the same instance of `Lbry`
import apiPublishCallViaWeb from 'lbrytv/setup/publish'; import apiPublishCallViaWeb from 'web/setup/publish';
const { DEFAULT_LANGUAGE } = require('../config.js');
// Sentry error logging setup // Sentry error logging setup
// Will only work if you have a SENTRY_AUTH_TOKEN env // Will only work if you have a SENTRY_AUTH_TOKEN env
@ -59,7 +60,7 @@ if (process.env.SDK_API_URL) {
let sdkAPIHost = process.env.SDK_API_HOST || process.env.SDK_API_URL; let sdkAPIHost = process.env.SDK_API_HOST || process.env.SDK_API_URL;
// @if TARGET='web' // @if TARGET='web'
sdkAPIHost = LBRY_TV_API; sdkAPIHost = LBRY_WEB_API;
// @endif // @endif
export const SDK_API_PATH = `${sdkAPIHost}/api/v1`; export const SDK_API_PATH = `${sdkAPIHost}/api/v1`;
@ -122,7 +123,7 @@ Lbryio.setOverride(
'new', 'new',
{ {
auth_token: '', auth_token: '',
language: 'en', language: DEFAULT_LANGUAGE,
app_id: status.installation_id, app_id: status.installation_id,
}, },
'post' 'post'
@ -278,6 +279,9 @@ function AppWrapper() {
// @if TARGET='app' // @if TARGET='app'
sessionStorage.setItem('startup', true); sessionStorage.setItem('startup', true);
// @endif // @endif
if (process.env.DEFAULT_LANGUAGE) {
app.store.dispatch(doFetchLanguage(process.env.DEFAULT_LANGUAGE));
}
app.store.dispatch(doUpdateIsNightAsync()); app.store.dispatch(doUpdateIsNightAsync());
app.store.dispatch(doDaemonReady()); app.store.dispatch(doDaemonReady());
app.store.dispatch(doBlackListedOutpointsSubscribe()); app.store.dispatch(doBlackListedOutpointsSubscribe());

View file

@ -10,7 +10,7 @@ import analytics from 'analytics';
import HiddenNsfw from 'component/common/hidden-nsfw'; import HiddenNsfw from 'component/common/hidden-nsfw';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import * as CS from 'constants/claim_search'; import * as CS from 'constants/claim_search';
import Ads from 'lbrytv/component/ads'; import Ads from 'web/component/ads';
type Props = { type Props = {
location: { search: string }, location: { search: string },

View file

@ -1,76 +1,13 @@
// @flow // @flow
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import Icon from 'component/common/icon';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import * as CS from 'constants/claim_search';
import React from 'react'; import React from 'react';
import moment from 'moment';
import Page from 'component/page'; import Page from 'component/page';
import Button from 'component/button'; import Button from 'component/button';
import ClaimTilesDiscover from 'component/claimTilesDiscover'; import ClaimTilesDiscover from 'component/claimTilesDiscover';
import I18nMessage from 'component/i18nMessage'; import I18nMessage from 'component/i18nMessage';
import { parseURI } from 'lbry-redux';
import { toCapitalCase } from 'util/string';
const PAID_BETA_CHANNEL_IDS_KEY = [
'4ee7cfaf1fc50a6df858ed0b99c278d633bccca9',
'5af39f818f668d8c00943c9326c5201c4fe3c423',
'cda9c4e92f19d6fe0764524a2012056e06ca2055',
'760da3ba3dd85830a843beaaed543a89b7a367e7',
'40c36948f0da072dcba3e4833e90f71e16de78be',
'e8f68563d242f6ac9784dcbc41dd86c28a9391d6',
'7236fc5d2783ea7314d9076ae6c8a250e3992d1a',
'8627af93c1a1219150f06b698f4b33e6ed2f1c1e',
'c5b0b17838df2f6c31162f64d55f60f34ae8bfc6',
'f576d5dba905fc179de880c3fe3eb3281ea74f59',
'97dd77c93c9603cbb2583f3589f7f5a6c92baa43',
'f399d873e0c37cf24de9569b5f22bbb30a5c6709',
'dba870d0620d41b2b9a152c961e0c06cf875ccfc',
'ca1fd651c9d14bf2e5088bb2aa0146ee7aeb2ae0',
'50ad846a4b1543b847bf3fdafb7b45f6b2f5844c',
'e09ff5abe9fb44dd0dd0576894a6db60a6211603',
'7b6f7517f6b816827d076fa0eaad550aa315a4e7',
'2068452c41d8da3bd68961335da0072a99258a1a',
'3645cf2f5d0bdac0523f945be1c3ff60758f7845',
'4da85b12244839d6368b9290f1619ff9514ab2a8',
'4ad942982e43326c7700b1b6443049b3cfd82161',
'55304f219244abf82f684f759cc0c7769242f3b4',
'8f42e5b592bb7f7a03f4a94a86a41b1236bb099f',
'e2a014d885a48f5be2dc6409610996337312facb',
'c18996ca488753f714d36d4654715927c1d7f9c2',
'ebc4214424cfa683a7046e1f794fea1e44788d84',
'06b6d6d6a893fb589ec2ded948f5122856921ed5',
'07e4546674268fc0222b2ca22d31d0549dc217ee',
'060940e41973d4f7f16d72a2733138e931c35f41',
'f8d6eccd887c9cebd36b1d42aa349279b7f5c3ed',
'68098b8426f967b8d04cc566348b5c128823219e',
'2bfe6cdb24a21bdc1b76fb7c416edd50e9e85945',
'1f9bb08bfa2259629f4aaa9ed40f97e9a41b6fa1',
'2f20148495612946675fe1c8ea99171e4d950b81',
'bc6938fa1e09e840056c2e831abf9664f397c472',
'2a6194792beac5130641e932b5ac6e5a99b5ca4f',
'185ba2bd547a5e4a77d29fe6c1484f47db5e058f',
'29cc7f6081268eaa5b3f2946e0cd0b952a94812c',
'ffdc62ac2f7549398d3aca9d2119e83d80d588d5',
'd7a4d2808074b0c55d6b239f69d90e7a4930f943',
'd58aa4a0b2f6c2504c3abce8de3f1afb71800acc',
'77ae23dc7eb8a75609881d4548a79e4935a89d37',
'f79bce8a60fbece671f6265adc39f6469f3b9b8c',
'051995fdf0af634e4911704057a551e9392e62b1',
'b0e489f986c345aef23c4a48d91cbcf5a6fdb9ac',
'825aa21c8c0bda4ded3e69a69238763c8cfcc13b',
'49389450b1241f5d8f4c8c4271a3eb56bba33965',
'f3b9973e1725ecb50da3e6fa4d47343c98ef0382',
'321b33d22c8e24ef207e3f357a4573f6a56611f3',
'20d694ada07e740c6fa43a8c324cb7d6e362b5ee',
'cf7792c2a37d0d76aaaff84aff0b99a8c791429d',
'8316ac90764fedf3147799b7b81a6575a9cc398e',
'8972a1bd06de5186e5e89292b05aac8aaa817791',
'5da63df97c8255ae94a88940695b8471657dd5a1',
'f3da2196b5151570d980b34d311ee0973225a68e',
'7644fdb8342624f6c647c79de25610801573fa68',
];
import getHomepage from 'homepage';
type Props = { type Props = {
authenticated: boolean, authenticated: boolean,
followedTags: Array<Tag>, followedTags: Array<Tag>,
@ -89,172 +26,15 @@ function HomePage(props: Props) {
const showPersonalizedChannels = (authenticated || !IS_WEB) && subscribedChannels && subscribedChannels.length > 0; const showPersonalizedChannels = (authenticated || !IS_WEB) && subscribedChannels && subscribedChannels.length > 0;
const showPersonalizedTags = (authenticated || !IS_WEB) && followedTags && followedTags.length > 0; const showPersonalizedTags = (authenticated || !IS_WEB) && followedTags && followedTags.length > 0;
const showIndividualTags = showPersonalizedTags && followedTags.length < 5; const showIndividualTags = showPersonalizedTags && followedTags.length < 5;
let rowData: Array<RowDataItem> = [];
const lbrytvPaidBetaRow = { const rowData: Array<RowDataItem> = getHomepage(
title: '#lbrytvpaidbeta', authenticated,
link: `/$/${PAGES.DISCOVER}?${CS.TAGS_KEY}=lbrytvpaidbeta&fee_amount=>0&${CS.CLAIM_TYPE}=${CS.CLAIM_STREAM}&${ showPersonalizedChannels,
CS.CHANNEL_IDS_KEY showPersonalizedTags,
}=${PAID_BETA_CHANNEL_IDS_KEY.join(',')}`, subscribedChannels,
help: ( followedTags,
<div className="claim-grid__help"> showIndividualTags
<Icon );
icon={ICONS.HELP}
tooltip
customTooltipText={__(
'Check your rewards page to see if you qualify for paid content reimbursement. Only content in this section qualifies.'
)}
/>
</div>
),
options: {
feeAmount: '>0',
claimType: ['stream'],
tags: ['lbrytvpaidbeta', 'lbry tvpaidbeta'],
pageSize: 8,
channelIds: PAID_BETA_CHANNEL_IDS_KEY,
},
};
// if you are following channels, always show that first
if (showPersonalizedChannels) {
let releaseTime = `>${Math.floor(
moment()
.subtract(1, 'year')
.startOf('week')
.unix()
)}`;
// Warning - hack below
// If users are following more than 20 channels or tags, limit results to stuff less than 6 months old
// This helps with timeout issues for users that are following a ton of stuff
// https://github.com/lbryio/lbry-sdk/issues/2420
if (subscribedChannels.length > 20) {
releaseTime = `>${Math.floor(
moment()
.subtract(6, 'months')
.startOf('week')
.unix()
)}`;
}
rowData.push({
title: 'Recent From Following',
link: `/$/${PAGES.CHANNELS_FOLLOWING}`,
options: {
orderBy: ['release_time'],
releaseTime: releaseTime,
pageSize: subscribedChannels.length > 3 ? 8 : 4,
channelIds: subscribedChannels.map(subscription => {
const { channelClaimId } = parseURI(subscription.uri);
return channelClaimId;
}),
},
});
}
if (showPersonalizedTags && !showIndividualTags) {
rowData.push({
title: 'Trending For Your Tags',
link: `/$/${PAGES.TAGS_FOLLOWING}`,
options: {
tags: followedTags.map(tag => tag.name),
claimType: ['stream'],
},
});
}
if (showPersonalizedTags && showIndividualTags) {
followedTags.forEach((tag: Tag) => {
rowData.push({
title: `Trending for #${toCapitalCase(tag.name)}`,
link: `/$/${PAGES.DISCOVER}?t=${tag.name}`,
options: {
pageSize: 4,
tags: [tag.name],
claimType: ['stream'],
},
});
});
}
if (authenticated) {
rowData.push(lbrytvPaidBetaRow);
}
rowData.push({
title: 'Top Content from Today',
link: `/$/${PAGES.DISCOVER}?${CS.ORDER_BY_KEY}=${CS.ORDER_BY_TOP}&${CS.FRESH_KEY}=${CS.FRESH_DAY}`,
options: {
pageSize: showPersonalizedChannels || showPersonalizedTags ? 4 : 8,
orderBy: ['effective_amount'],
claimType: ['stream'],
releaseTime: `>${Math.floor(
moment()
.subtract(1, 'day')
.startOf('day')
.unix()
)}`,
},
});
rowData.push({
title: 'Trending On LBRY',
link: `/$/${PAGES.DISCOVER}`,
options: {
pageSize: showPersonalizedChannels || showPersonalizedTags ? 4 : 8,
},
});
if (!showPersonalizedChannels) {
rowData.push({
title: 'Top Channels On LBRY',
link: `/$/${PAGES.DISCOVER}?claim_type=channel&${CS.ORDER_BY_KEY}=${CS.ORDER_BY_TOP}&${CS.FRESH_KEY}=${CS.FRESH_ALL}`,
options: {
orderBy: ['effective_amount'],
claimType: ['channel'],
},
});
}
if (!authenticated) {
rowData.push(lbrytvPaidBetaRow);
}
rowData.push({
title: 'Trending Classics',
link: `/$/${PAGES.DISCOVER}?${CS.ORDER_BY_KEY}=${CS.ORDER_BY_TRENDING}&${CS.FRESH_KEY}=${CS.FRESH_WEEK}`,
options: {
pageSize: 4,
claimType: ['stream'],
releaseTime: `<${Math.floor(
moment()
.subtract(6, 'month')
.startOf('day')
.unix()
)}`,
},
});
rowData.push({
title: 'Latest From @lbrycast',
link: `/@lbrycast:4`,
options: {
orderBy: ['release_time'],
pageSize: 4,
channelIds: ['4c29f8b013adea4d5cca1861fb2161d5089613ea'],
},
});
rowData.push({
title: 'Latest From @lbry',
link: `/@lbry:3f`,
options: {
orderBy: ['release_time'],
pageSize: 4,
channelIds: ['3fda836a92faaceedfe398225fb9b2ee2ed1f01a'],
},
});
return ( return (
<Page> <Page>

View file

@ -9,7 +9,7 @@ import Page from 'component/page';
import SearchOptions from 'component/searchOptions'; import SearchOptions from 'component/searchOptions';
import Button from 'component/button'; import Button from 'component/button';
import ClaimUri from 'component/claimUri'; import ClaimUri from 'component/claimUri';
import Ads from 'lbrytv/component/ads'; import Ads from 'web/component/ads';
type AdditionalOptions = { type AdditionalOptions = {
isBackgroundSearch: boolean, isBackgroundSearch: boolean,

View file

@ -20,7 +20,7 @@ import {
homepageReducer, homepageReducer,
statsReducer, statsReducer,
syncReducer, syncReducer,
lbrytvReducer, webReducer,
} from 'lbryinc'; } from 'lbryinc';
import appReducer from 'redux/reducers/app'; import appReducer from 'redux/reducers/app';
import contentReducer from 'redux/reducers/content'; import contentReducer from 'redux/reducers/content';
@ -51,5 +51,5 @@ export default history =>
user: userReducer, user: userReducer,
wallet: walletReducer, wallet: walletReducer,
sync: syncReducer, sync: syncReducer,
lbrytv: lbrytvReducer, web: webReducer,
}); });

View file

@ -4,6 +4,7 @@ import analytics from 'analytics';
import SUPPORTED_LANGUAGES from 'constants/supported_languages'; import SUPPORTED_LANGUAGES from 'constants/supported_languages';
import { launcher } from 'util/autoLaunch'; import { launcher } from 'util/autoLaunch';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
const { DEFAULT_LANGUAGE } = require('config');
export const IS_MAC = process.platform === 'darwin'; export const IS_MAC = process.platform === 'darwin';
const UPDATE_IS_NIGHT_INTERVAL = 5 * 60 * 1000; const UPDATE_IS_NIGHT_INTERVAL = 5 * 60 * 1000;
@ -164,6 +165,27 @@ export function doSetDarkTime(value, options) {
}; };
} }
export function doFetchLanguage(language) {
return (dispatch, getState) => {
const { settings } = getState();
if (settings.language !== language || (settings.loadedLanguages && !settings.loadedLanguages.includes(language))) {
// this should match the behavior/logic in index-web.html
fetch('https://lbry.com/i18n/get/lbry-desktop/app-strings/' + language + '.json')
.then(r => r.json())
.then(j => {
window.i18n_messages[language] = j;
dispatch({
type: LOCAL_ACTIONS.DOWNLOAD_LANGUAGE_SUCCESS,
data: {
language,
},
});
});
}
};
}
export function doSetLanguage(language) { export function doSetLanguage(language) {
return (dispatch, getState) => { return (dispatch, getState) => {
const { settings } = getState(); const { settings } = getState();
@ -186,8 +208,8 @@ export function doSetLanguage(language) {
dispatch(doSetClientSetting(SETTINGS.LANGUAGE, language)); dispatch(doSetClientSetting(SETTINGS.LANGUAGE, language));
}) })
.catch(e => { .catch(e => {
window.localStorage.setItem(SETTINGS.LANGUAGE, 'en'); window.localStorage.setItem(SETTINGS.LANGUAGE, DEFAULT_LANGUAGE);
dispatch(doSetClientSetting(SETTINGS.LANGUAGE, 'en')); dispatch(doSetClientSetting(SETTINGS.LANGUAGE, DEFAULT_LANGUAGE));
const languageName = SUPPORTED_LANGUAGES[language] ? SUPPORTED_LANGUAGES[language] : language; const languageName = SUPPORTED_LANGUAGES[language] ? SUPPORTED_LANGUAGES[language] : language;
dispatch( dispatch(
doToast({ doToast({

251
ui/util/homepage.js Normal file
View file

@ -0,0 +1,251 @@
// @flow
import * as PAGES from 'constants/pages';
import * as CS from 'constants/claim_search';
import * as ICONS from 'constants/icons';
import { parseURI } from 'lbry-redux';
import moment from 'moment';
import { toCapitalCase } from 'util/string';
import React from 'react';
import Icon from 'component/common/icon';
type RowDataItem = {
title: string,
link?: string,
help?: any,
options?: {},
};
export default function getHomePageRowData(
authenticated: boolean,
showPersonalizedChannels: boolean,
showPersonalizedTags: boolean,
subscribedChannels: Array<Subscription>,
followedTags: Array<Tag>,
showIndividualTags: boolean
) {
let rowData: Array<RowDataItem> = [];
const individualTagDataItems: Array<RowDataItem> = [];
const PAID_BETA_CHANNEL_IDS_KEY = [
'4ee7cfaf1fc50a6df858ed0b99c278d633bccca9',
'5af39f818f668d8c00943c9326c5201c4fe3c423',
'cda9c4e92f19d6fe0764524a2012056e06ca2055',
'760da3ba3dd85830a843beaaed543a89b7a367e7',
'40c36948f0da072dcba3e4833e90f71e16de78be',
'e8f68563d242f6ac9784dcbc41dd86c28a9391d6',
'7236fc5d2783ea7314d9076ae6c8a250e3992d1a',
'8627af93c1a1219150f06b698f4b33e6ed2f1c1e',
'c5b0b17838df2f6c31162f64d55f60f34ae8bfc6',
'f576d5dba905fc179de880c3fe3eb3281ea74f59',
'97dd77c93c9603cbb2583f3589f7f5a6c92baa43',
'f399d873e0c37cf24de9569b5f22bbb30a5c6709',
'dba870d0620d41b2b9a152c961e0c06cf875ccfc',
'ca1fd651c9d14bf2e5088bb2aa0146ee7aeb2ae0',
'50ad846a4b1543b847bf3fdafb7b45f6b2f5844c',
'e09ff5abe9fb44dd0dd0576894a6db60a6211603',
'7b6f7517f6b816827d076fa0eaad550aa315a4e7',
'2068452c41d8da3bd68961335da0072a99258a1a',
'3645cf2f5d0bdac0523f945be1c3ff60758f7845',
'4da85b12244839d6368b9290f1619ff9514ab2a8',
'4ad942982e43326c7700b1b6443049b3cfd82161',
'55304f219244abf82f684f759cc0c7769242f3b4',
'8f42e5b592bb7f7a03f4a94a86a41b1236bb099f',
'e2a014d885a48f5be2dc6409610996337312facb',
'c18996ca488753f714d36d4654715927c1d7f9c2',
'ebc4214424cfa683a7046e1f794fea1e44788d84',
'06b6d6d6a893fb589ec2ded948f5122856921ed5',
'07e4546674268fc0222b2ca22d31d0549dc217ee',
'060940e41973d4f7f16d72a2733138e931c35f41',
'f8d6eccd887c9cebd36b1d42aa349279b7f5c3ed',
'68098b8426f967b8d04cc566348b5c128823219e',
'2bfe6cdb24a21bdc1b76fb7c416edd50e9e85945',
'1f9bb08bfa2259629f4aaa9ed40f97e9a41b6fa1',
'2f20148495612946675fe1c8ea99171e4d950b81',
'bc6938fa1e09e840056c2e831abf9664f397c472',
'2a6194792beac5130641e932b5ac6e5a99b5ca4f',
'185ba2bd547a5e4a77d29fe6c1484f47db5e058f',
'29cc7f6081268eaa5b3f2946e0cd0b952a94812c',
'ffdc62ac2f7549398d3aca9d2119e83d80d588d5',
'd7a4d2808074b0c55d6b239f69d90e7a4930f943',
'd58aa4a0b2f6c2504c3abce8de3f1afb71800acc',
'77ae23dc7eb8a75609881d4548a79e4935a89d37',
'f79bce8a60fbece671f6265adc39f6469f3b9b8c',
'051995fdf0af634e4911704057a551e9392e62b1',
'b0e489f986c345aef23c4a48d91cbcf5a6fdb9ac',
'825aa21c8c0bda4ded3e69a69238763c8cfcc13b',
'49389450b1241f5d8f4c8c4271a3eb56bba33965',
'f3b9973e1725ecb50da3e6fa4d47343c98ef0382',
'321b33d22c8e24ef207e3f357a4573f6a56611f3',
'20d694ada07e740c6fa43a8c324cb7d6e362b5ee',
'cf7792c2a37d0d76aaaff84aff0b99a8c791429d',
'8316ac90764fedf3147799b7b81a6575a9cc398e',
'8972a1bd06de5186e5e89292b05aac8aaa817791',
'5da63df97c8255ae94a88940695b8471657dd5a1',
'f3da2196b5151570d980b34d311ee0973225a68e',
'7644fdb8342624f6c647c79de25610801573fa68',
];
const TV_PAID_BETA_ROW = {
title: '#lbrytvpaidbeta',
link: `/$/${PAGES.DISCOVER}?${CS.TAGS_KEY}=lbrytvpaidbeta&fee_amount=>0&${CS.CLAIM_TYPE}=${CS.CLAIM_STREAM}&${
CS.CHANNEL_IDS_KEY
}=${PAID_BETA_CHANNEL_IDS_KEY.join(',')}`,
help: (
<div className="claim-grid__help">
<Icon
icon={ICONS.HELP}
tooltip
customTooltipText={__(
'Check your rewards page to see if you qualify for paid content reimbursement. Only content in this section qualifies.'
)}
/>
</div>
),
options: {
feeAmount: '>0',
claimType: ['stream'],
tags: ['lbrytvpaidbeta', 'lbry tvpaidbeta'],
pageSize: 8,
channelIds: PAID_BETA_CHANNEL_IDS_KEY,
},
};
if (followedTags.length) {
followedTags.forEach((tag: Tag) => {
individualTagDataItems.push({
title: `Trending for #${toCapitalCase(tag.name)}`,
link: `/$/${PAGES.DISCOVER}?t=${tag.name}`,
options: {
pageSize: 4,
tags: [tag.name],
claimType: ['stream'],
},
});
});
}
const RECENT_FROM_FOLLOWING = {
title: 'Recent From Following',
link: `/$/${PAGES.CHANNELS_FOLLOWING}`,
options: {
orderBy: ['release_time'],
releaseTime:
subscribedChannels.length > 20
? `>${Math.floor(
moment()
.subtract(6, 'months')
.startOf('week')
.unix()
)}`
: `>${Math.floor(
moment()
.subtract(1, 'year')
.startOf('week')
.unix()
)}`,
pageSize: subscribedChannels.length > 3 ? 8 : 4,
channelIds: subscribedChannels.map((subscription: Subscription) => {
const { channelClaimId } = parseURI(subscription.uri);
return channelClaimId;
}),
},
};
const TOP_CONTENT_TODAY = {
title: 'Top Content from Today',
link: `/$/${PAGES.DISCOVER}?${CS.ORDER_BY_KEY}=${CS.ORDER_BY_TOP}&${CS.FRESH_KEY}=${CS.FRESH_DAY}`,
options: {
pageSize: showPersonalizedChannels || showPersonalizedTags ? 4 : 8,
orderBy: ['effective_amount'],
claimType: ['stream'],
releaseTime: `>${Math.floor(
moment()
.subtract(1, 'day')
.startOf('day')
.unix()
)}`,
},
};
const TOP_CHANNELS = {
title: 'Top Channels On LBRY',
link: `/$/${PAGES.DISCOVER}?claim_type=channel&${CS.ORDER_BY_KEY}=${CS.ORDER_BY_TOP}&${CS.FRESH_KEY}=${CS.FRESH_ALL}`,
options: {
orderBy: ['effective_amount'],
claimType: ['channel'],
},
};
const TRENDING_CLASSICS = {
title: 'Trending Classics',
link: `/$/${PAGES.DISCOVER}?${CS.ORDER_BY_KEY}=${CS.ORDER_BY_TRENDING}&${CS.FRESH_KEY}=${CS.FRESH_WEEK}`,
options: {
pageSize: 4,
claimType: ['stream'],
releaseTime: `<${Math.floor(
moment()
.subtract(6, 'month')
.startOf('day')
.unix()
)}`,
},
};
const TRENDING_ON_LBRY = {
title: 'Trending On LBRY',
link: `/$/${PAGES.DISCOVER}`,
options: {
pageSize: showPersonalizedChannels || showPersonalizedTags ? 4 : 8,
},
};
const TRENDING_FOR_TAGS = {
title: 'Trending For Your Tags',
link: `/$/${PAGES.TAGS_FOLLOWING}`,
options: {
tags: followedTags.map(tag => tag.name),
claimType: ['stream'],
},
};
const LATEST_FROM_LBRY = {
title: 'Latest From @lbry',
link: `/@lbry:3f`,
options: {
orderBy: ['release_time'],
pageSize: 4,
channelIds: ['3fda836a92faaceedfe398225fb9b2ee2ed1f01a'],
},
};
const LATEST_FROM_LBRYCAST = {
title: 'Latest From @lbrycast',
link: `/@lbrycast:4`,
options: {
orderBy: ['release_time'],
pageSize: 4,
channelIds: ['3fda836a92faaceedfe398225fb9b2ee2ed1f01a'],
},
};
if (showPersonalizedChannels) rowData.push(RECENT_FROM_FOLLOWING);
if (showPersonalizedTags && !showIndividualTags) rowData.push(TRENDING_FOR_TAGS);
if (showPersonalizedTags && showIndividualTags) {
individualTagDataItems.forEach((item: RowDataItem) => {
rowData.push(item);
});
}
if (authenticated) {
rowData.push(TV_PAID_BETA_ROW);
}
rowData.push(TOP_CONTENT_TODAY);
rowData.push(TRENDING_ON_LBRY);
if (!showPersonalizedChannels) rowData.push(TOP_CHANNELS);
if (!authenticated) {
rowData.push(TV_PAID_BETA_ROW);
}
rowData.push(TRENDING_CLASSICS);
rowData.push(LATEST_FROM_LBRYCAST);
rowData.push(LATEST_FROM_LBRY);
return rowData;
}

View file

@ -1,9 +1,9 @@
const { URL, LBRY_TV_STREAMING_API } = require('../../config'); const { URL, LBRY_WEB_STREAMING_API } = require('../../config');
const CONTINENT_COOKIE = 'continent'; const CONTINENT_COOKIE = 'continent';
function generateStreamUrl(claimName, claimId) { function generateStreamUrl(claimName, claimId) {
return `${LBRY_TV_STREAMING_API}/content/claims/${claimName}/${claimId}/stream`; return `${LBRY_WEB_STREAMING_API}/content/claims/${claimName}/${claimId}/stream`;
} }
function generateEmbedUrl(claimName, claimId, includeStartTime, startTime) { function generateEmbedUrl(claimName, claimId, includeStartTime, startTime) {

15
web/.env.defaults Normal file
View file

@ -0,0 +1,15 @@
# Copy this file to .env to make modifications
WEBPACK_WEB_PORT=9090
WEBPACK_ELECTRON_PORT=9091
WEB_SERVER_PORT=1337
DOMAIN=lbry.tv
URL=https://lbry.tv
SITE_TITLE=lbry.tv
LOGO_TITLE=lbry.tv
LBRY_WEB_API=https://api.lbry.tv
LBRY_WEB_STREAMING_API=https://cdn.lbryplayer.xyz
WELCOME_VERSION=1.0
DEFAULT_LANGUAGE=en
# If true, supply copy example to homepage.js in
CUSTOM_HOMEPAGE=false

View file

@ -1,7 +1,5 @@
const config = require('../../config'); const config = require('../../config');
const PAGES = require('../../ui/constants/pages');
const { formatInAppUrl } = require('../../ui/util/url'); const { formatInAppUrl } = require('../../ui/util/url');
const { parseURI } = require('lbry-redux');
async function redirectMiddleware(ctx, next) { async function redirectMiddleware(ctx, next) {
const requestHost = ctx.host; const requestHost = ctx.host;

View file

@ -1,5 +1,5 @@
const { URL } = require('../../config.js'); const { URL } = require('../../config.js');
const { generateEmbedUrl, generateStreamUrl } = require('../../ui/util/lbrytv'); const { generateEmbedUrl, generateStreamUrl } = require('../../ui/util/web');
const PAGES = require('../../ui/constants/pages'); const PAGES = require('../../ui/constants/pages');
const { getClaim } = require('./chainquery'); const { getClaim } = require('./chainquery');
const { parseURI } = require('lbry-redux'); const { parseURI } = require('lbry-redux');

View file

@ -1,5 +1,5 @@
const { getHtml } = require('./html'); const { getHtml } = require('./html');
const { generateStreamUrl } = require('../../ui/util/lbrytv'); const { generateStreamUrl } = require('../../ui/util/web');
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const Router = require('@koa/router'); const Router = require('@koa/router');

View file

@ -1,7 +1,7 @@
const callable = () => { const callable = () => {
throw new Error('Need to fix this stub'); throw new Error('Need to fix this stub');
}; };
const { DEFAULT_LANGUAGE } = require('../../config.js');
export const remote = { export const remote = {
dialog: { dialog: {
showOpenDialog: callable, showOpenDialog: callable,
@ -9,7 +9,9 @@ export const remote = {
getCurrentWindow: callable, getCurrentWindow: callable,
app: { app: {
getAppPath: callable, getAppPath: callable,
getLocale: () => 'en', getLocale: () => {
return DEFAULT_LANGUAGE;
},
}, },
BrowserWindow: { BrowserWindow: {
getFocusedWindow: callable, getFocusedWindow: callable,

View file

@ -1,5 +1,5 @@
function logWarning(method) { function logWarning(method) {
if (NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
console.error(`Called fs.${method} on lbry.tv. This should be removed.`); console.error(`Called fs.${method} on lbry.tv. This should be removed.`);
} }
} }

View file

@ -1,4 +1,4 @@
const { WEBPACK_WEB_PORT, LBRY_TV_API } = require('../config.js'); const { WEBPACK_WEB_PORT, LBRY_WEB_API } = require('../config.js');
const path = require('path'); const path = require('path');
const merge = require('webpack-merge'); const merge = require('webpack-merge');
const baseConfig = require('../webpack.base.config.js'); const baseConfig = require('../webpack.base.config.js');
@ -34,7 +34,7 @@ let plugins = [
]), ]),
new DefinePlugin({ new DefinePlugin({
IS_WEB: JSON.stringify(true), IS_WEB: JSON.stringify(true),
'process.env.SDK_API_URL': JSON.stringify(process.env.SDK_API_URL || LBRY_TV_API), 'process.env.SDK_API_URL': JSON.stringify(process.env.SDK_API_URL || LBRY_WEB_API),
}), }),
new ProvidePlugin({ new ProvidePlugin({
__: ['i18n.js', '__'], __: ['i18n.js', '__'],

View file

@ -1,8 +1,8 @@
const path = require('path'); const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const merge = require('webpack-merge'); const Dotenv = require('dotenv-webpack');
const { DefinePlugin, ProvidePlugin } = require('webpack'); const { DefinePlugin } = require('webpack');
const { getIfUtils, removeEmpty } = require('webpack-config-utils'); const { getIfUtils } = require('webpack-config-utils');
const TerserPlugin = require('terser-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin');
const NODE_ENV = process.env.NODE_ENV || 'development'; const NODE_ENV = process.env.NODE_ENV || 'development';
@ -63,7 +63,8 @@ let baseConfig = {
modules: [UI_ROOT, 'node_modules', __dirname], modules: [UI_ROOT, 'node_modules', __dirname],
extensions: ['.js', '.jsx', '.json', '.scss'], extensions: ['.js', '.jsx', '.json', '.scss'],
alias: { alias: {
config: path.resolve(__dirname, './config.js'), config: path.resolve(__dirname, 'config.js'),
homepage: process.env.CUSTOM_HOMEPAGE === 'true' ? path.resolve(__dirname, 'custom/homepage.js') : ('util/homepage.js'),
lbryinc: 'lbryinc/dist/bundle.es.js', lbryinc: 'lbryinc/dist/bundle.es.js',
// Build optimizations for 'redux-persist-transform-filter' // Build optimizations for 'redux-persist-transform-filter'
'redux-persist-transform-filter': 'redux-persist-transform-filter/index.js', 'redux-persist-transform-filter': 'redux-persist-transform-filter/index.js',
@ -88,7 +89,12 @@ let baseConfig = {
'process.env.LBRY_API_URL': JSON.stringify(process.env.LBRY_API_URL), 'process.env.LBRY_API_URL': JSON.stringify(process.env.LBRY_API_URL),
'process.env.SENTRY_AUTH_TOKEN': JSON.stringify(process.env.SENTRY_AUTH_TOKEN), 'process.env.SENTRY_AUTH_TOKEN': JSON.stringify(process.env.SENTRY_AUTH_TOKEN),
}), }),
new Dotenv({
allowEmptyValues: true, // allow empty variables (e.g. `FOO=`) (treat it as empty string, rather than missing)
systemvars: true, // load all the predefined 'process.env' variables which will trump anything local per dotenv specs.
silent: false, // hide any errors
defaults: true, // load '.env.defaults' as the default values if empty.
}),
], ],
}; };
module.exports = baseConfig; module.exports = baseConfig;

View file

@ -3544,10 +3544,29 @@ dot-prop@^5.2.0:
dependencies: dependencies:
is-obj "^2.0.0" is-obj "^2.0.0"
dotenv-defaults@^1.0.2, dotenv-defaults@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/dotenv-defaults/-/dotenv-defaults-1.1.1.tgz#032c024f4b5906d9990eb06d722dc74cc60ec1bd"
integrity sha512-6fPRo9o/3MxKvmRZBD3oNFdxODdhJtIy1zcJeUSCs6HCy4tarUpd+G67UTU9tF6OWXeSPqsm4fPAB+2eY9Rt9Q==
dependencies:
dotenv "^6.2.0"
dotenv-expand@^5.1.0: dotenv-expand@^5.1.0:
version "5.1.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
dotenv-webpack@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/dotenv-webpack/-/dotenv-webpack-1.8.0.tgz#7ca79cef2497dd4079d43e81e0796bc9d0f68a5e"
integrity sha512-o8pq6NLBehtrqA8Jv8jFQNtG9nhRtVqmoD4yWbgUyoU3+9WBlPe+c2EAiaJok9RB28QvrWvdWLZGeTT5aATDMg==
dependencies:
dotenv-defaults "^1.0.2"
dotenv@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064"
integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==
dotenv@^8.0.0, dotenv@^8.2.0: dotenv@^8.0.0, dotenv@^8.2.0:
version "8.2.0" version "8.2.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
@ -6186,9 +6205,9 @@ lbry-redux@lbryio/lbry-redux#d2079111b3372eb926d2753991bed860e57a970a:
reselect "^3.0.0" reselect "^3.0.0"
uuid "^3.3.2" uuid "^3.3.2"
lbryinc@lbryio/lbryinc#6a52f8026cdc7cd56d200fb5c46f852e0139bbeb: lbryinc@lbryio/lbryinc#8bdf1ac44d03cef9d798df92e3192a4591845f17:
version "0.0.1" version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/6a52f8026cdc7cd56d200fb5c46f852e0139bbeb" resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/8bdf1ac44d03cef9d798df92e3192a4591845f17"
dependencies: dependencies:
reselect "^3.0.0" reselect "^3.0.0"