Add scam alert message when opening development tools. #1500
61 changed files with 623 additions and 448 deletions
.eslintrc.json.travis.ymlCHANGELOG.mdCONTRIBUTING.mdREADME.mdpackage.json
src
main
renderer
component
button
categoryList
common
fileActions
fileCard
fileDownloadLink
fileList
filePrice
inviteNew
publishForm
rewardLink
router
subscribeButton
userVerify
video/internal
viewOnWebButton
constants
index.jsmodal
page
redux
rewards.jsscss
util
static/themes
yarn.lock
|
@ -36,6 +36,7 @@
|
||||||
"func-names": ["warn", "as-needed"],
|
"func-names": ["warn", "as-needed"],
|
||||||
"jsx-a11y/label-has-for": 0,
|
"jsx-a11y/label-has-for": 0,
|
||||||
"import/prefer-default-export": 0,
|
"import/prefer-default-export": 0,
|
||||||
"no-return-assign": 0
|
"no-return-assign": 0,
|
||||||
|
"react/require-default-props": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
20
.travis.yml
20
.travis.yml
|
@ -1,16 +1,19 @@
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: osx
|
- os: osx
|
||||||
|
env: TARGET=mac
|
||||||
osx_image: xcode9.2
|
osx_image: xcode9.2
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js: "9"
|
node_js: "9"
|
||||||
env:
|
|
||||||
- ELECTRON_CACHE=$HOME/.cache/electron
|
|
||||||
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
|
|
||||||
- os: linux
|
- os: linux
|
||||||
|
env: TARGET=windows
|
||||||
services: docker
|
services: docker
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js: "9"
|
node_js: "9"
|
||||||
|
- os: linux
|
||||||
|
env: TARGET=linux
|
||||||
|
language: node_js
|
||||||
|
node_js: "9"
|
||||||
cache: false
|
cache: false
|
||||||
before_install:
|
before_install:
|
||||||
- |
|
- |
|
||||||
|
@ -20,6 +23,7 @@ before_install:
|
||||||
else
|
else
|
||||||
sudo apt-get -qq update
|
sudo apt-get -qq update
|
||||||
sudo apt-get install -y libsecret-1-dev
|
sudo apt-get install -y libsecret-1-dev
|
||||||
|
sudo apt-get install --no-install-recommends -y gcc-multilib g++-multilib
|
||||||
curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.6.0
|
curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.6.0
|
||||||
export PATH="$HOME/.yarn/bin:$PATH"
|
export PATH="$HOME/.yarn/bin:$PATH"
|
||||||
fi
|
fi
|
||||||
|
@ -27,17 +31,15 @@ before_script:
|
||||||
- git lfs pull
|
- git lfs pull
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
|
if [ "$TARGET" == "windows" ]; then
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
--env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|GH_|GITHUB_|BT_|AWS_|STRIP|BUILD_') \
|
--env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|GH_|GITHUB_|BT_|AWS_|STRIP|BUILD_') \
|
||||||
-v ${PWD}:/project \
|
-v ${PWD}:/project \
|
||||||
-v ~/.cache/electron:/root/.cache/electron \
|
|
||||||
-v ~/.cache/electron-builder:/root/.cache/electron-builder \
|
|
||||||
electronuserland/builder:wine \
|
electronuserland/builder:wine \
|
||||||
/bin/bash -c "yarn --link-duplicates --pure-lockfile && yarn build --linux --win --publish onTag"
|
/bin/bash -c "yarn --link-duplicates --pure-lockfile && yarn build --win --publish onTag";
|
||||||
else
|
|
||||||
yarn build --publish onTag
|
|
||||||
fi
|
fi
|
||||||
|
- if [ "$TARGET" == "mac" ]; then yarn build --publish onTag; fi
|
||||||
|
- if [ "$TARGET" == "linux" ]; then yarn --link-duplicates --pure-lockfile && yarn build --linux --publish onTag; fi
|
||||||
branches:
|
branches:
|
||||||
except:
|
except:
|
||||||
- "/^v\\d+\\.\\d+\\.\\d+$/"
|
- "/^v\\d+\\.\\d+\\.\\d+$/"
|
||||||
|
|
|
@ -14,11 +14,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
||||||
* Pre-fill publish URL after clicking "Put something here" link ([#1303](https://github.com/lbryio/lbry-app/pull/1303))
|
* Pre-fill publish URL after clicking "Put something here" link ([#1303](https://github.com/lbryio/lbry-app/pull/1303))
|
||||||
* Add Danger JS to automate code reviews ([#1289](https://github.com/lbryio/lbry-app/pull/1289))
|
* Add Danger JS to automate code reviews ([#1289](https://github.com/lbryio/lbry-app/pull/1289))
|
||||||
* Autoplay downloaded and free media ([#584](https://github.com/lbryio/lbry-app/pull/1453))
|
* Autoplay downloaded and free media ([#584](https://github.com/lbryio/lbry-app/pull/1453))
|
||||||
|
* Add 'Go to page' input on channel pagination ([#1166](https://github.com/lbryio/lbry-app/pull/1166))
|
||||||
|
* Add "View on web" button on file/channel pages with spee.ch link ([#1222](https://github.com/lbryio/lbry-app/pull/1222))
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Add flair to snackbar ([#1313](https://github.com/lbryio/lbry-app/pull/1313))
|
* Add flair to snackbar ([#1313](https://github.com/lbryio/lbry-app/pull/1313))
|
||||||
* Made font in price badge larger ([#1420](https://github.com/lbryio/lbry-app/pull/1420))
|
* Made font in price badge larger ([#1420](https://github.com/lbryio/lbry-app/pull/1420))
|
||||||
* Store subscriptions in internal database ([#1424](https://github.com/lbryio/lbry-app/pull/1424))
|
* Store subscriptions in internal database ([#1424](https://github.com/lbryio/lbry-app/pull/1424))
|
||||||
|
* Move rewards logic to interal api ([#1509](https://github.com/lbryio/lbry-app/pull/1509))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* Fix content-type not shown correctly in file description ([#863](https://github.com/lbryio/lbry-app/pull/863))
|
* Fix content-type not shown correctly in file description ([#863](https://github.com/lbryio/lbry-app/pull/863))
|
||||||
|
@ -57,7 +60,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
||||||
* App category for Linux ([#877](https://github.com/lbryio/lbry-app/pull/877))
|
* App category for Linux ([#877](https://github.com/lbryio/lbry-app/pull/877))
|
||||||
* Add YouTube Sync reward ([#1147](https://github.com/lbryio/lbry-app/pull/1147))
|
* Add YouTube Sync reward ([#1147](https://github.com/lbryio/lbry-app/pull/1147))
|
||||||
* Retain previous screen sizing on startup ([#338](https://github.com/lbryio/lbry-app/issues/338))
|
* Retain previous screen sizing on startup ([#338](https://github.com/lbryio/lbry-app/issues/338))
|
||||||
* Add 'Go to page' input on channel pagination ([#1166](https://github.com/lbryio/lbry-app/pull/1166))
|
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -372,7 +374,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
||||||
* Removed placeholder values from price selection form fields, which was causing confusion that these were real values (#426)
|
* Removed placeholder values from price selection form fields, which was causing confusion that these were real values (#426)
|
||||||
* Fixed showing "other currency" help tip in publish form, which was caused due to not "setting" state for price
|
* Fixed showing "other currency" help tip in publish form, which was caused due to not "setting" state for price
|
||||||
* Publish page now properly checks for all required fields are filled
|
* Publish page now properly checks for all required fields are filled
|
||||||
* Fixed pagination styling for pages > 5 (#416)
|
|
||||||
* Fixed sizing on squat videos (#419)
|
* Fixed sizing on squat videos (#419)
|
||||||
* Support claims no longer show up on Published page (#384)
|
* Support claims no longer show up on Published page (#384)
|
||||||
* Fixed rendering of small prices (#461)
|
* Fixed rendering of small prices (#461)
|
||||||
|
@ -410,7 +411,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
||||||
### Added
|
### Added
|
||||||
* Replaced horizontal scrollbars with scroll arrows
|
* Replaced horizontal scrollbars with scroll arrows
|
||||||
* Featured weekly reward content shows with an orange star
|
* Featured weekly reward content shows with an orange star
|
||||||
* Added pagination to channel pages
|
|
||||||
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -146,6 +146,8 @@ There are a few tools integrated to the project that will ease the process of de
|
||||||
manner and, therefore, not begin working on anything reserved (or updated) within the last 3 days.
|
manner and, therefore, not begin working on anything reserved (or updated) within the last 3 days.
|
||||||
If someone has been officially assigned an issue via Github's assignment system, it is also not
|
If someone has been officially assigned an issue via Github's assignment system, it is also not
|
||||||
available. Contributors are encouraged to ask if they have any questions about issue availability.
|
available. Contributors are encouraged to ask if they have any questions about issue availability.
|
||||||
|
* The [changelog](https://github.com/lbryio/lbry-app/blob/master/CHANGELOG.md) should be updated to
|
||||||
|
include a referene to the fix/change/addition. See previous entries for format.
|
||||||
* Once the pull request is visible in the LBRY repo, a LBRY team member will review it and make sure
|
* Once the pull request is visible in the LBRY repo, a LBRY team member will review it and make sure
|
||||||
it is up to our standards. At this point, the contributor may have to change his or her code based
|
it is up to our standards. At this point, the contributor may have to change his or her code based
|
||||||
on our suggestions and comments.
|
on our suggestions and comments.
|
||||||
|
|
|
@ -50,7 +50,7 @@ distributable packages.
|
||||||
|
|
||||||
#### Resetting your Packages
|
#### Resetting your Packages
|
||||||
|
|
||||||
If the app isn't building, or `yarn xxx` commands aren't working you may need to just reset your `node_modules`. To do so you can run: `rm -r node_modules && yarn` or `del node_modules && yarn` on Windows.
|
If the app isn't building, or `yarn xxx` commands aren't working you may need to just reset your `node_modules`. To do so you can run: `rm -r node_modules && yarn` or `del /s /q node_modules && yarn` on Windows.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
"formik": "^0.10.4",
|
"formik": "^0.10.4",
|
||||||
"hast-util-sanitize": "^1.1.2",
|
"hast-util-sanitize": "^1.1.2",
|
||||||
"keytar": "^4.2.1",
|
"keytar": "^4.2.1",
|
||||||
"lbry-redux": "lbryio/lbry-redux#30c18725d8c6c141c30c57f0a324d0abb8963b99",
|
"lbry-redux": "lbryio/lbry-redux#fb27fb66387043be1b7962cd9439845fa73d7d8e",
|
||||||
"localforage": "^1.7.1",
|
"localforage": "^1.7.1",
|
||||||
"mixpanel-browser": "^2.17.1",
|
"mixpanel-browser": "^2.17.1",
|
||||||
"moment": "^2.22.0",
|
"moment": "^2.22.0",
|
||||||
|
|
|
@ -3,7 +3,6 @@ import isDev from 'electron-is-dev';
|
||||||
import windowStateKeeper from 'electron-window-state';
|
import windowStateKeeper from 'electron-window-state';
|
||||||
|
|
||||||
import setupBarMenu from './menu/setupBarMenu';
|
import setupBarMenu from './menu/setupBarMenu';
|
||||||
import setupContextMenu from './menu/setupContextMenu';
|
|
||||||
|
|
||||||
export default appState => {
|
export default appState => {
|
||||||
// Get primary display dimensions from Electron.
|
// Get primary display dimensions from Electron.
|
||||||
|
@ -73,7 +72,6 @@ export default appState => {
|
||||||
}
|
}
|
||||||
|
|
||||||
setupBarMenu();
|
setupBarMenu();
|
||||||
setupContextMenu(window);
|
|
||||||
|
|
||||||
window.on('close', event => {
|
window.on('close', event => {
|
||||||
if (!appState.isQuitting && !appState.autoUpdateAccepted) {
|
if (!appState.isQuitting && !appState.autoUpdateAccepted) {
|
||||||
|
|
|
@ -179,11 +179,11 @@ ipcMain.on('version-info-requested', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let result = '';
|
let result = '';
|
||||||
|
const onSuccess = res => {
|
||||||
const req = https.get(Object.assign(opts, url.parse(latestReleaseAPIURL)), res => {
|
|
||||||
res.on('data', data => {
|
res.on('data', data => {
|
||||||
result += data;
|
result += data;
|
||||||
});
|
});
|
||||||
|
|
||||||
res.on('end', () => {
|
res.on('end', () => {
|
||||||
const tagName = JSON.parse(result).tag_name;
|
const tagName = JSON.parse(result).tag_name;
|
||||||
const [, remoteVersion] = tagName.match(/^v([\d.]+(?:-?rc\d+)?)$/);
|
const [, remoteVersion] = tagName.match(/^v([\d.]+(?:-?rc\d+)?)$/);
|
||||||
|
@ -202,14 +202,27 @@ ipcMain.on('version-info-requested', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
|
|
||||||
req.on('error', err => {
|
const requestLatestRelease = (apiUrl, alreadyRedirected = false) => {
|
||||||
console.log('Failed to get current version from GitHub. Error:', err);
|
const req = https.get(Object.assign(opts, url.parse(apiUrl)), res => {
|
||||||
if (rendererWindow) {
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
||||||
rendererWindow.webContents.send('version-info-received', null);
|
requestLatestRelease(res.headers.location, true);
|
||||||
}
|
} else {
|
||||||
});
|
onSuccess(res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (alreadyRedirected) return;
|
||||||
|
req.on('error', err => {
|
||||||
|
console.log('Failed to get current version from GitHub. Error:', err);
|
||||||
|
if (rendererWindow) {
|
||||||
|
rendererWindow.webContents.send('version-info-received', null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
requestLatestRelease(latestReleaseAPIURL);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('get-auth-token', event => {
|
ipcMain.on('get-auth-token', event => {
|
||||||
|
@ -245,8 +258,11 @@ const isSecondInstance = app.makeSingleInstance(argv => {
|
||||||
// path, so we just strip it off.
|
// path, so we just strip it off.
|
||||||
// - In a URI with a claim ID, like lbry://channel#claimid, Windows interprets the hash mark as
|
// - In a URI with a claim ID, like lbry://channel#claimid, Windows interprets the hash mark as
|
||||||
// an anchor and converts it to lbry://channel/#claimid. We remove the slash here as well.
|
// an anchor and converts it to lbry://channel/#claimid. We remove the slash here as well.
|
||||||
|
// - ? also interpreted as an anchor, remove slash also.
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
URI = URI.replace(/\/$/, '').replace('/#', '#');
|
URI = URI.replace(/\/$/, '')
|
||||||
|
.replace('/#', '#')
|
||||||
|
.replace('/?', '?');
|
||||||
}
|
}
|
||||||
|
|
||||||
rendererWindow.webContents.send('open-uri-requested', URI);
|
rendererWindow.webContents.send('open-uri-requested', URI);
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
// @flow
|
|
||||||
import { Menu, BrowserWindow } from 'electron';
|
|
||||||
import isDev from 'electron-is-dev';
|
|
||||||
|
|
||||||
export default (rendererWindow: BrowserWindow) => {
|
|
||||||
rendererWindow.webContents.on('context-menu', (e, params) => {
|
|
||||||
const { x, y } = params;
|
|
||||||
|
|
||||||
const template = [{ role: 'cut' }, { role: 'copy' }, { role: 'paste' }];
|
|
||||||
|
|
||||||
const developmentTemplateAddition = [
|
|
||||||
{ type: 'separator' },
|
|
||||||
{
|
|
||||||
label: 'Inspect element',
|
|
||||||
click: () => {
|
|
||||||
rendererWindow.inspectElement(x, y);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isDev) {
|
|
||||||
template.push(...developmentTemplateAddition);
|
|
||||||
}
|
|
||||||
|
|
||||||
Menu.buildFromTemplate(template).popup();
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -22,6 +22,7 @@ type Props = {
|
||||||
button: ?string, // primary, secondary, alt, link
|
button: ?string, // primary, secondary, alt, link
|
||||||
noPadding: ?boolean, // to remove padding and allow circular buttons
|
noPadding: ?boolean, // to remove padding and allow circular buttons
|
||||||
uppercase: ?boolean,
|
uppercase: ?boolean,
|
||||||
|
iconColor: ?string,
|
||||||
};
|
};
|
||||||
|
|
||||||
class Button extends React.PureComponent<Props> {
|
class Button extends React.PureComponent<Props> {
|
||||||
|
@ -48,6 +49,7 @@ class Button extends React.PureComponent<Props> {
|
||||||
type,
|
type,
|
||||||
noPadding,
|
noPadding,
|
||||||
uppercase,
|
uppercase,
|
||||||
|
iconColor,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -82,10 +84,10 @@ class Button extends React.PureComponent<Props> {
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<span className="btn__content">
|
<span className="btn__content">
|
||||||
{icon && <Icon icon={icon} />}
|
{icon && <Icon icon={icon} iconColor={iconColor} />}
|
||||||
{label && <span className="btn__label">{label}</span>}
|
{label && <span className="btn__label">{label}</span>}
|
||||||
{children && children}
|
{children && children}
|
||||||
{iconRight && <Icon icon={iconRight} />}
|
{iconRight && <Icon icon={iconRight} iconColor={iconColor} />}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
18
src/renderer/component/categoryList/index.js
Normal file
18
src/renderer/component/categoryList/index.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { doFetchClaimsByChannel } from 'redux/actions/content';
|
||||||
|
import {
|
||||||
|
makeSelectClaimsInChannelForCurrentPage,
|
||||||
|
makeSelectFetchingChannelClaims,
|
||||||
|
} from 'lbry-redux';
|
||||||
|
import CategoryList from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
channelClaims: makeSelectClaimsInChannelForCurrentPage(props.categoryLink)(state),
|
||||||
|
fetching: makeSelectFetchingChannelClaims(props.categoryLink)(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
fetchChannel: channel => dispatch(doFetchClaimsByChannel(channel)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(CategoryList);
|
|
@ -5,11 +5,15 @@ import ToolTip from 'component/common/tooltip';
|
||||||
import FileCard from 'component/fileCard';
|
import FileCard from 'component/fileCard';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import * as icons from 'constants/icons';
|
import * as icons from 'constants/icons';
|
||||||
|
import Claim from 'types/claim';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
category: string,
|
category: string,
|
||||||
names: Array<string>,
|
names: Array<string>,
|
||||||
categoryLink?: string,
|
categoryLink: ?string,
|
||||||
|
fetching: boolean,
|
||||||
|
channelClaims: Array<Claim>,
|
||||||
|
fetchChannel: string => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
|
@ -18,6 +22,11 @@ type State = {
|
||||||
};
|
};
|
||||||
|
|
||||||
class CategoryList extends React.PureComponent<Props, State> {
|
class CategoryList extends React.PureComponent<Props, State> {
|
||||||
|
static defaultProps = {
|
||||||
|
names: [],
|
||||||
|
categoryLink: '',
|
||||||
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -32,6 +41,11 @@ class CategoryList extends React.PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
const { fetching, categoryLink, fetchChannel } = this.props;
|
||||||
|
if (!fetching) {
|
||||||
|
fetchChannel(categoryLink);
|
||||||
|
}
|
||||||
|
|
||||||
const cardRow = this.rowItems;
|
const cardRow = this.rowItems;
|
||||||
if (cardRow) {
|
if (cardRow) {
|
||||||
const cards = cardRow.getElementsByTagName('section');
|
const cards = cardRow.getElementsByTagName('section');
|
||||||
|
@ -109,6 +123,9 @@ class CategoryList extends React.PureComponent<Props, State> {
|
||||||
|
|
||||||
// check if a card is fully visible horizontally
|
// check if a card is fully visible horizontally
|
||||||
isCardVisible = (section: HTMLElement) => {
|
isCardVisible = (section: HTMLElement) => {
|
||||||
|
if (!section) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const rect = section.getBoundingClientRect();
|
const rect = section.getBoundingClientRect();
|
||||||
const isVisible = rect.left >= 0 && rect.right <= window.innerWidth;
|
const isVisible = rect.left >= 0 && rect.right <= window.innerWidth;
|
||||||
return isVisible;
|
return isVisible;
|
||||||
|
@ -189,7 +206,7 @@ class CategoryList extends React.PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { category, names, categoryLink } = this.props;
|
const { category, categoryLink, names, channelClaims } = this.props;
|
||||||
const { canScrollNext, canScrollPrevious } = this.state;
|
const { canScrollNext, canScrollPrevious } = this.state;
|
||||||
|
|
||||||
// The lint was throwing an error saying we should use <button> instead of <a>
|
// The lint was throwing an error saying we should use <button> instead of <a>
|
||||||
|
@ -235,6 +252,12 @@ class CategoryList extends React.PureComponent<Props, State> {
|
||||||
className="card-row__scrollhouse"
|
className="card-row__scrollhouse"
|
||||||
>
|
>
|
||||||
{names && names.map(name => <FileCard key={name} uri={normalizeURI(name)} />)}
|
{names && names.map(name => <FileCard key={name} uri={normalizeURI(name)} />)}
|
||||||
|
|
||||||
|
{channelClaims && channelClaims.length
|
||||||
|
? channelClaims.map(claim => (
|
||||||
|
<FileCard key={claim.claim_id} uri={`lbry://${claim.name}#${claim.claim_id}`} />
|
||||||
|
))
|
||||||
|
: null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
|
@ -62,7 +62,6 @@ export class FormFieldPrice extends React.PureComponent<Props> {
|
||||||
name={`${name}_currency`}
|
name={`${name}_currency`}
|
||||||
type="select"
|
type="select"
|
||||||
id={`${name}_currency`}
|
id={`${name}_currency`}
|
||||||
className="form-field"
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={this.handleCurrencyChange}
|
onChange={this.handleCurrencyChange}
|
||||||
value={price.currency}
|
value={price.currency}
|
||||||
|
|
|
@ -42,7 +42,7 @@ export class FormField extends React.PureComponent<Props> {
|
||||||
if (type) {
|
if (type) {
|
||||||
if (type === 'select') {
|
if (type === 'select') {
|
||||||
input = (
|
input = (
|
||||||
<select id={name} {...inputProps}>
|
<select className="form-field__select" id={name} {...inputProps}>
|
||||||
{children}
|
{children}
|
||||||
</select>
|
</select>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,7 +4,6 @@ import * as React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
centered?: boolean,
|
|
||||||
children: React.Node,
|
children: React.Node,
|
||||||
padded?: boolean,
|
padded?: boolean,
|
||||||
verticallyCentered?: boolean,
|
verticallyCentered?: boolean,
|
||||||
|
@ -22,7 +21,6 @@ export class FormRow extends React.PureComponent<Props> {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classnames('form-row', {
|
className={classnames('form-row', {
|
||||||
'form-row--centered': centered,
|
|
||||||
'form-row--padded': padded,
|
'form-row--padded': padded,
|
||||||
'form-row--vertically-centered': verticallyCentered,
|
'form-row--vertically-centered': verticallyCentered,
|
||||||
'form-row--stretch': stretch,
|
'form-row--stretch': stretch,
|
||||||
|
|
|
@ -1,26 +1,52 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
// import * as icons from 'constants/icons';
|
|
||||||
import * as FeatherIcons from 'react-feather';
|
import * as FeatherIcons from 'react-feather';
|
||||||
import * as icons from 'constants/icons';
|
import * as icons from 'constants/icons';
|
||||||
|
import Tooltip from 'component/common/tooltip';
|
||||||
|
|
||||||
const RED_COLOR = '#e2495e';
|
const RED_COLOR = '#e2495e';
|
||||||
|
const PURPLE_COLOR = '#8165b0';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
icon: string,
|
icon: string,
|
||||||
|
tooltip?: string, // tooltip direction
|
||||||
|
iconColor?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
class IconComponent extends React.PureComponent<Props> {
|
class IconComponent extends React.PureComponent<Props> {
|
||||||
// TODO: Move all icons to constants and add titles for all
|
getTooltip = (icon: string) => {
|
||||||
// Add some some sort of hover flyout with the title?
|
switch (icon) {
|
||||||
|
case icons.FEATURED:
|
||||||
|
return __('Featured content. Earn rewards for watching.');
|
||||||
|
case icons.LOCAL:
|
||||||
|
return __('This file is downloaded.');
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getIconColor = (color: string) => {
|
||||||
|
switch (color) {
|
||||||
|
case 'red':
|
||||||
|
return RED_COLOR;
|
||||||
|
case 'purple':
|
||||||
|
return PURPLE_COLOR;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { icon } = this.props;
|
const { icon, tooltip, iconColor } = this.props;
|
||||||
const Icon = FeatherIcons[icon];
|
const Icon = FeatherIcons[icon];
|
||||||
|
|
||||||
|
if (!Icon) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let color;
|
let color;
|
||||||
if (icon === icons.HEART || icon === icons.FEATURED) {
|
if (iconColor) {
|
||||||
color = RED_COLOR;
|
color = this.getIconColor(iconColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
let size = 14;
|
let size = 14;
|
||||||
|
@ -28,7 +54,19 @@ class IconComponent extends React.PureComponent<Props> {
|
||||||
size = 20;
|
size = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Icon ? <Icon size={size} className="icon" color={color} /> : null;
|
let tooltipText;
|
||||||
|
if (tooltip) {
|
||||||
|
tooltipText = this.getTooltip(icon);
|
||||||
|
}
|
||||||
|
const inner = <Icon size={size} className="icon" color={color} />;
|
||||||
|
|
||||||
|
return tooltip ? (
|
||||||
|
<Tooltip icon body={tooltipText} direction={tooltip}>
|
||||||
|
{inner}
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
inner
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,55 +1,38 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import * as React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Icon from 'component/common/icon';
|
|
||||||
import Button from 'component/button';
|
|
||||||
import * as icons from 'constants/icons';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
body: string,
|
body: string,
|
||||||
label: string,
|
label?: string,
|
||||||
|
children: ?React.Node,
|
||||||
|
icon: ?boolean,
|
||||||
|
direction: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
class ToolTip extends React.PureComponent<Props> {
|
||||||
showTooltip: boolean,
|
static defaultProps = {
|
||||||
};
|
direction: 'bottom',
|
||||||
|
};
|
||||||
class ToolTip extends React.PureComponent<Props, State> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
showTooltip: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
(this: any).handleClick = this.handleClick.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick() {
|
|
||||||
const { showTooltip } = this.state;
|
|
||||||
|
|
||||||
if (!showTooltip) {
|
|
||||||
document.addEventListener('click', this.handleClick);
|
|
||||||
} else {
|
|
||||||
document.removeEventListener('click', this.handleClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
showTooltip: !showTooltip,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { label, body } = this.props;
|
const { children, label, body, icon, direction } = this.props;
|
||||||
const { showTooltip } = this.state;
|
|
||||||
|
const tooltipContent = children || label;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="tooltip">
|
<span
|
||||||
<Button button="link" className="help tooltip__link" onClick={this.handleClick}>
|
className={classnames('tooltip', {
|
||||||
{label}
|
'tooltip--label': label && !icon,
|
||||||
{showTooltip && <Icon icon={icons.CLOSE} />}
|
'tooltip--icon': icon,
|
||||||
</Button>
|
'tooltip--top': direction === 'top',
|
||||||
<div className={classnames('tooltip__body', { hidden: !showTooltip })}>{body}</div>
|
'tooltip--right': direction === 'right',
|
||||||
|
'tooltip--bottom': direction === 'bottom',
|
||||||
|
'tooltip--left': direction === 'left',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{tooltipContent}
|
||||||
|
<span className="tooltip__body">{body}</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import FileDownloadLink from 'component/fileDownloadLink';
|
|
||||||
import { MODALS } from 'lbry-redux';
|
import { MODALS } from 'lbry-redux';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import * as icons from 'constants/icons';
|
import * as icons from 'constants/icons';
|
||||||
|
@ -27,21 +26,21 @@ class FileActions extends React.PureComponent<Props> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={classnames('card__actions', { 'card__actions--vertical': vertical })}>
|
<section className={classnames('card__actions', { 'card__actions--vertical': vertical })}>
|
||||||
<FileDownloadLink uri={uri} />
|
|
||||||
{showDelete && (
|
{showDelete && (
|
||||||
<Button
|
<Button
|
||||||
className="btn--file-actions"
|
button="alt"
|
||||||
icon={icons.TRASH}
|
icon={icons.TRASH}
|
||||||
description={__('Delete')}
|
iconColor="red"
|
||||||
|
label={__('Delete')}
|
||||||
onClick={() => openModal({ id: MODALS.CONFIRM_FILE_REMOVE }, { uri })}
|
onClick={() => openModal({ id: MODALS.CONFIRM_FILE_REMOVE }, { uri })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!claimIsMine && (
|
{!claimIsMine && (
|
||||||
<Button
|
<Button
|
||||||
className="btn--file-actions"
|
button="alt"
|
||||||
icon={icons.REPORT}
|
icon={icons.REPORT}
|
||||||
href={`https://lbry.io/dmca?claim_id=${claimId}`}
|
href={`https://lbry.io/dmca?claim_id=${claimId}`}
|
||||||
description={__('Report content')}
|
label={__('Report content')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { normalizeURI } from 'lbry-redux';
|
import { normalizeURI, convertToShareLink } from 'lbry-redux';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import CardMedia from 'component/cardMedia';
|
import CardMedia from 'component/cardMedia';
|
||||||
import TruncatedText from 'component/common/truncated-text';
|
import TruncatedText from 'component/common/truncated-text';
|
||||||
|
@ -9,6 +9,7 @@ import FilePrice from 'component/filePrice';
|
||||||
import UriIndicator from 'component/uriIndicator';
|
import UriIndicator from 'component/uriIndicator';
|
||||||
import * as icons from 'constants/icons';
|
import * as icons from 'constants/icons';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import { openCopyLinkMenu } from '../../util/contextMenu';
|
||||||
|
|
||||||
// TODO: iron these out
|
// TODO: iron these out
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -60,6 +61,9 @@ class FileCard extends React.PureComponent<Props> {
|
||||||
const thumbnail = metadata && metadata.thumbnail ? metadata.thumbnail : null;
|
const thumbnail = metadata && metadata.thumbnail ? metadata.thumbnail : null;
|
||||||
const shouldObscureNsfw = obscureNsfw && metadata && metadata.nsfw;
|
const shouldObscureNsfw = obscureNsfw && metadata && metadata.nsfw;
|
||||||
const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
|
const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
|
||||||
|
const handleContextMenu = event => {
|
||||||
|
openCopyLinkMenu(convertToShareLink(uri), event);
|
||||||
|
};
|
||||||
|
|
||||||
// We should be able to tab through cards
|
// We should be able to tab through cards
|
||||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||||
|
@ -72,6 +76,7 @@ class FileCard extends React.PureComponent<Props> {
|
||||||
'card--link': !pending,
|
'card--link': !pending,
|
||||||
'card--pending': pending,
|
'card--pending': pending,
|
||||||
})}
|
})}
|
||||||
|
onContextMenu={handleContextMenu}
|
||||||
>
|
>
|
||||||
<CardMedia nsfw={shouldObscureNsfw} thumbnail={thumbnail} />
|
<CardMedia nsfw={shouldObscureNsfw} thumbnail={thumbnail} />
|
||||||
<div className="card-media__internal-links">{showPrice && <FilePrice uri={uri} />}</div>
|
<div className="card-media__internal-links">{showPrice && <FilePrice uri={uri} />}</div>
|
||||||
|
@ -98,14 +103,16 @@ class FileCard extends React.PureComponent<Props> {
|
||||||
<div className="card__title--small">
|
<div className="card__title--small">
|
||||||
<TruncatedText lines={3}>{title}</TruncatedText>
|
<TruncatedText lines={3}>{title}</TruncatedText>
|
||||||
</div>
|
</div>
|
||||||
<div className="card__subtitle card__subtitle--file-info">
|
<div className="card__subtitle">
|
||||||
{pending ? (
|
{pending ? (
|
||||||
<div>Pending...</div>
|
<div>Pending...</div>
|
||||||
) : (
|
) : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<UriIndicator uri={uri} link />
|
<UriIndicator uri={uri} link />
|
||||||
{isRewardContent && <Icon icon={icons.FEATURED} />}
|
<div>
|
||||||
{fileInfo && <Icon icon={icons.LOCAL} />}
|
{isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />}
|
||||||
|
{fileInfo && <Icon icon={icons.LOCAL} />}
|
||||||
|
</div>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,7 +5,6 @@ import {
|
||||||
makeSelectLoadingForUri,
|
makeSelectLoadingForUri,
|
||||||
makeSelectCostInfoForUri,
|
makeSelectCostInfoForUri,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { doFetchAvailability } from 'redux/actions/availability';
|
|
||||||
import { doOpenFileInShell } from 'redux/actions/file';
|
import { doOpenFileInShell } from 'redux/actions/file';
|
||||||
import { doPurchaseUri, doStartDownload } from 'redux/actions/content';
|
import { doPurchaseUri, doStartDownload } from 'redux/actions/content';
|
||||||
import { doPause } from 'redux/actions/media';
|
import { doPause } from 'redux/actions/media';
|
||||||
|
@ -20,7 +19,6 @@ const select = (state, props) => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
checkAvailability: uri => dispatch(doFetchAvailability(uri)),
|
|
||||||
openInShell: path => dispatch(doOpenFileInShell(path)),
|
openInShell: path => dispatch(doOpenFileInShell(path)),
|
||||||
purchaseUri: uri => dispatch(doPurchaseUri(uri)),
|
purchaseUri: uri => dispatch(doPurchaseUri(uri)),
|
||||||
restartDownload: (uri, outpoint) => dispatch(doStartDownload(uri, outpoint)),
|
restartDownload: (uri, outpoint) => dispatch(doStartDownload(uri, outpoint)),
|
||||||
|
|
|
@ -1,21 +1,29 @@
|
||||||
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import classnames from 'classnames';
|
|
||||||
import * as icons from 'constants/icons';
|
import * as icons from 'constants/icons';
|
||||||
|
|
||||||
class FileDownloadLink extends React.PureComponent {
|
type Props = {
|
||||||
componentWillMount() {
|
uri: string,
|
||||||
this.checkAvailability(this.props.uri);
|
downloading: boolean,
|
||||||
}
|
fileInfo: ?{
|
||||||
|
written_bytes: number,
|
||||||
componentWillReceiveProps(nextProps) {
|
total_bytes: number,
|
||||||
this.checkAvailability(nextProps.uri);
|
outpoint: number,
|
||||||
this.restartDownload(nextProps);
|
download_path: string,
|
||||||
}
|
completed: boolean,
|
||||||
|
},
|
||||||
restartDownload(props) {
|
loading: boolean,
|
||||||
const { downloading, fileInfo, uri, restartDownload } = props;
|
costInfo: ?{},
|
||||||
|
restartDownload: (string, number) => void,
|
||||||
|
openInShell: string => void,
|
||||||
|
purchaseUri: string => void,
|
||||||
|
doPause: () => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileDownloadLink extends React.PureComponent<Props> {
|
||||||
|
componentWillUpdate() {
|
||||||
|
const { downloading, fileInfo, uri, restartDownload } = this.props;
|
||||||
if (
|
if (
|
||||||
!downloading &&
|
!downloading &&
|
||||||
fileInfo &&
|
fileInfo &&
|
||||||
|
@ -27,12 +35,7 @@ class FileDownloadLink extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAvailability(uri) {
|
uri: ?string;
|
||||||
if (!this._uri || uri !== this._uri) {
|
|
||||||
this._uri = uri;
|
|
||||||
this.props.checkAvailability(uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
@ -47,8 +50,10 @@ class FileDownloadLink extends React.PureComponent {
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const openFile = () => {
|
const openFile = () => {
|
||||||
openInShell(fileInfo.download_path);
|
if (fileInfo) {
|
||||||
doPause();
|
openInShell(fileInfo.download_path);
|
||||||
|
doPause();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading || downloading) {
|
if (loading || downloading) {
|
||||||
|
@ -56,21 +61,11 @@ class FileDownloadLink extends React.PureComponent {
|
||||||
fileInfo && fileInfo.written_bytes
|
fileInfo && fileInfo.written_bytes
|
||||||
? fileInfo.written_bytes / fileInfo.total_bytes * 100
|
? fileInfo.written_bytes / fileInfo.total_bytes * 100
|
||||||
: 0;
|
: 0;
|
||||||
const label = fileInfo ? progress.toFixed(0) + __('% complete') : __('Connecting...');
|
const label = fileInfo
|
||||||
|
? __('Downloading: ') + progress.toFixed(0) + __('% complete')
|
||||||
|
: __('Connecting...');
|
||||||
|
|
||||||
return (
|
return <span className="file-download">{label}</span>;
|
||||||
<div className="file-download btn__content">
|
|
||||||
<div
|
|
||||||
className={classnames('file-download__overlay', {
|
|
||||||
btn__content: !!progress,
|
|
||||||
})}
|
|
||||||
style={{ width: `${progress}%` }}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</div>
|
|
||||||
{label}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else if (fileInfo === null && !downloading) {
|
} else if (fileInfo === null && !downloading) {
|
||||||
if (!costInfo) {
|
if (!costInfo) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -78,9 +73,10 @@ class FileDownloadLink extends React.PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className="btn--file-actions"
|
button="alt"
|
||||||
description={__('Download')}
|
label={__('Download')}
|
||||||
icon={icons.DOWNLOAD}
|
icon={icons.DOWNLOAD}
|
||||||
|
iconColor="purple"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
purchaseUri(uri);
|
purchaseUri(uri);
|
||||||
}}
|
}}
|
||||||
|
@ -89,8 +85,9 @@ class FileDownloadLink extends React.PureComponent {
|
||||||
} else if (fileInfo && fileInfo.download_path) {
|
} else if (fileInfo && fileInfo.download_path) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className="btn--file-actions"
|
button="alt"
|
||||||
description={__('Open')}
|
iconColor="purple"
|
||||||
|
label={__('Open File')}
|
||||||
icon={icons.OPEN}
|
icon={icons.OPEN}
|
||||||
onClick={() => openFile()}
|
onClick={() => openFile()}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -8,6 +8,7 @@ type FileInfo = {
|
||||||
name: string,
|
name: string,
|
||||||
channelName: ?string,
|
channelName: ?string,
|
||||||
pending?: boolean,
|
pending?: boolean,
|
||||||
|
channel_claim_id: string,
|
||||||
value?: {
|
value?: {
|
||||||
publisherSignature: {
|
publisherSignature: {
|
||||||
certificateId: string,
|
certificateId: string,
|
||||||
|
@ -139,6 +140,8 @@ class FileList extends React.PureComponent<Props, State> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortFunctions: {};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { fileInfos, hideFilter, checkPending } = this.props;
|
const { fileInfos, hideFilter, checkPending } = this.props;
|
||||||
const { sortBy } = this.state;
|
const { sortBy } = this.state;
|
||||||
|
@ -149,27 +152,14 @@ class FileList extends React.PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sortFunctions[sortBy](fileInfos).forEach(fileInfo => {
|
this.sortFunctions[sortBy](fileInfos).forEach(fileInfo => {
|
||||||
const {
|
const { name: claimName, claim_name: claimNameDownloaded, claim_id: claimId } = fileInfo;
|
||||||
channel_name: channelName,
|
|
||||||
name: claimName,
|
|
||||||
claim_name: claimNameDownloaded,
|
|
||||||
claim_id: claimId,
|
|
||||||
} = fileInfo;
|
|
||||||
const uriParams = {};
|
const uriParams = {};
|
||||||
|
|
||||||
// This is unfortunate
|
// This is unfortunate
|
||||||
// https://github.com/lbryio/lbry/issues/1159
|
// https://github.com/lbryio/lbry/issues/1159
|
||||||
const name = claimName || claimNameDownloaded;
|
const name = claimName || claimNameDownloaded;
|
||||||
|
uriParams.contentName = name;
|
||||||
if (channelName) {
|
uriParams.claimId = claimId;
|
||||||
uriParams.channelName = channelName;
|
|
||||||
uriParams.contentName = name;
|
|
||||||
uriParams.claimId = this.getChannelSignature(fileInfo);
|
|
||||||
} else {
|
|
||||||
uriParams.claimId = claimId;
|
|
||||||
uriParams.claimName = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uri = buildURI(uriParams);
|
const uri = buildURI(uriParams);
|
||||||
|
|
||||||
content.push(<FileCard key={uri} uri={uri} checkPending={checkPending} />);
|
content.push(<FileCard key={uri} uri={uri} checkPending={checkPending} />);
|
||||||
|
|
|
@ -35,20 +35,14 @@ class FilePrice extends React.PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { costInfo, showFullPrice } = this.props;
|
const { costInfo, showFullPrice } = this.props;
|
||||||
|
|
||||||
const isEstimate = costInfo ? !costInfo.includesData : false;
|
return costInfo ? (
|
||||||
|
|
||||||
if (!costInfo) {
|
|
||||||
return <span className="credit-amount">PRICE</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CreditAmount
|
<CreditAmount
|
||||||
amount={costInfo.cost}
|
amount={costInfo.cost}
|
||||||
isEstimate={isEstimate}
|
isEstimate={!costInfo.includesData}
|
||||||
showFree
|
showFree
|
||||||
showFullPrice={showFullPrice}
|
showFullPrice={showFullPrice}
|
||||||
/>
|
/>
|
||||||
);
|
) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,17 @@
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import InviteNew from './view';
|
|
||||||
import {
|
import {
|
||||||
selectUserInvitesRemaining,
|
selectUserInvitesRemaining,
|
||||||
selectUserInviteNewIsPending,
|
selectUserInviteNewIsPending,
|
||||||
selectUserInviteNewErrorMessage,
|
selectUserInviteNewErrorMessage,
|
||||||
} from 'redux/selectors/user';
|
} from 'redux/selectors/user';
|
||||||
import rewards from 'rewards';
|
|
||||||
import { makeSelectRewardAmountByType } from 'redux/selectors/rewards';
|
|
||||||
|
|
||||||
import { doUserInviteNew } from 'redux/actions/user';
|
import { doUserInviteNew } from 'redux/actions/user';
|
||||||
|
import InviteNew from './view';
|
||||||
|
|
||||||
const select = state => {
|
const select = state => ({
|
||||||
const selectReward = makeSelectRewardAmountByType();
|
errorMessage: selectUserInviteNewErrorMessage(state),
|
||||||
|
invitesRemaining: selectUserInvitesRemaining(state),
|
||||||
return {
|
isPending: selectUserInviteNewIsPending(state),
|
||||||
errorMessage: selectUserInviteNewErrorMessage(state),
|
});
|
||||||
invitesRemaining: selectUserInvitesRemaining(state),
|
|
||||||
isPending: selectUserInviteNewIsPending(state),
|
|
||||||
rewardAmount: selectReward(state, { reward_type: rewards.TYPE_REFERRAL }),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
inviteNew: email => dispatch(doUserInviteNew(email)),
|
inviteNew: email => dispatch(doUserInviteNew(email)),
|
||||||
|
|
|
@ -29,8 +29,7 @@ class FormInviteNew extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { errorMessage, isPending, rewardAmount } = this.props;
|
const { errorMessage, isPending } = this.props;
|
||||||
const label = `${__('Get')} ${rewardAmount} LBC`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={this.handleSubmit}>
|
<Form onSubmit={this.handleSubmit}>
|
||||||
|
@ -49,7 +48,7 @@ class FormInviteNew extends React.PureComponent {
|
||||||
/>
|
/>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
<div className="card__actions">
|
<div className="card__actions">
|
||||||
<Submit label={label} disabled={isPending} />
|
<Submit label="Invite" disabled={isPending} />
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|
|
@ -29,6 +29,8 @@ type Props = {
|
||||||
currency: string,
|
currency: string,
|
||||||
},
|
},
|
||||||
channel: string,
|
channel: string,
|
||||||
|
channelId: ?string,
|
||||||
|
myChannels: Array<{ name: string }>,
|
||||||
name: ?string,
|
name: ?string,
|
||||||
tosAccepted: boolean,
|
tosAccepted: boolean,
|
||||||
updatePublishForm: UpdatePublishFormData => void,
|
updatePublishForm: UpdatePublishFormData => void,
|
||||||
|
@ -137,13 +139,14 @@ class PublishForm extends React.PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChannelChange(channelName: string) {
|
handleChannelChange(channelName: string) {
|
||||||
const { name, updatePublishForm } = this.props;
|
const { name, updatePublishForm, myChannels } = this.props;
|
||||||
|
const form = { channel: channelName };
|
||||||
|
const namedChannelClaim = myChannels.find(channel => channel.name === channelName);
|
||||||
|
form.channelId = namedChannelClaim ? namedChannelClaim.claim_id : '';
|
||||||
if (name) {
|
if (name) {
|
||||||
const uri = this.getNewUri(name, channelName);
|
form.uri = this.getNewUri(name, channelName);
|
||||||
updatePublishForm({ channel: channelName, uri });
|
|
||||||
} else {
|
|
||||||
updatePublishForm({ channel: channelName });
|
|
||||||
}
|
}
|
||||||
|
updatePublishForm(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBidChange(bid: number) {
|
handleBidChange(bid: number) {
|
||||||
|
@ -183,7 +186,7 @@ class PublishForm extends React.PureComponent<Props> {
|
||||||
description,
|
description,
|
||||||
language,
|
language,
|
||||||
nsfw,
|
nsfw,
|
||||||
channel,
|
channelId,
|
||||||
licenseType,
|
licenseType,
|
||||||
licenseUrl,
|
licenseUrl,
|
||||||
otherLicenseDescription,
|
otherLicenseDescription,
|
||||||
|
@ -217,7 +220,7 @@ class PublishForm extends React.PureComponent<Props> {
|
||||||
description,
|
description,
|
||||||
language,
|
language,
|
||||||
nsfw,
|
nsfw,
|
||||||
channel,
|
channelId,
|
||||||
license: publishingLicense,
|
license: publishingLicense,
|
||||||
licenseUrl: publishingLicenseUrl,
|
licenseUrl: publishingLicenseUrl,
|
||||||
otherLicenseDescription,
|
otherLicenseDescription,
|
||||||
|
|
|
@ -16,7 +16,7 @@ const makeSelect = () => {
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
errorMessage: selectError(state, props),
|
errorMessage: selectError(state, props),
|
||||||
isPending: selectIsPending(state, props),
|
isPending: selectIsPending(state, props),
|
||||||
reward: selectReward(state, props),
|
reward: selectReward(state, props.reward_type),
|
||||||
});
|
});
|
||||||
|
|
||||||
return select;
|
return select;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectCurrentPage, selectCurrentParams } from 'lbry-redux';
|
import { selectCurrentPage, selectCurrentParams, doNotify } from 'lbry-redux';
|
||||||
import Router from './view';
|
import Router from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
|
@ -7,4 +7,4 @@ const select = state => ({
|
||||||
currentPage: selectCurrentPage(state),
|
currentPage: selectCurrentPage(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, null)(Router);
|
export default connect(select, { doNotify })(Router);
|
||||||
|
|
|
@ -18,16 +18,21 @@ import InvitePage from 'page/invite';
|
||||||
import BackupPage from 'page/backup';
|
import BackupPage from 'page/backup';
|
||||||
import SubscriptionsPage from 'page/subscriptions';
|
import SubscriptionsPage from 'page/subscriptions';
|
||||||
|
|
||||||
const route = (page, routesMap) => {
|
const route = (props, page, routesMap) => {
|
||||||
const component = routesMap[page];
|
const component = routesMap[page];
|
||||||
|
if (!component) {
|
||||||
return component || DiscoverPage;
|
props.doNotify({
|
||||||
|
message: __('Invalid page requested'),
|
||||||
|
displayType: ['snackbar'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return component || routesMap.discover;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Router = props => {
|
const Router = props => {
|
||||||
const { currentPage, params } = props;
|
const { currentPage, params } = props;
|
||||||
|
|
||||||
return route(currentPage, {
|
return route(props, currentPage, {
|
||||||
auth: <AuthPage params={params} />,
|
auth: <AuthPage params={params} />,
|
||||||
backup: <BackupPage params={params} />,
|
backup: <BackupPage params={params} />,
|
||||||
channel: <ChannelPage params={params} />,
|
channel: <ChannelPage params={params} />,
|
||||||
|
|
|
@ -37,8 +37,9 @@ export default (props: Props) => {
|
||||||
|
|
||||||
return channelName && uri ? (
|
return channelName && uri ? (
|
||||||
<Button
|
<Button
|
||||||
|
iconColor="red"
|
||||||
icon={isSubscribed ? undefined : icons.HEART}
|
icon={isSubscribed ? undefined : icons.HEART}
|
||||||
button={isSubscribed ? 'danger' : 'alt'}
|
button="alt"
|
||||||
label={subscriptionLabel}
|
label={subscriptionLabel}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!subscriptions.length) {
|
if (!subscriptions.length) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { doNotify, MODALS } from 'lbry-redux';
|
||||||
import { doNavigate } from 'redux/actions/navigation';
|
import { doNavigate } from 'redux/actions/navigation';
|
||||||
import { doUserIdentityVerify } from 'redux/actions/user';
|
import { doUserIdentityVerify } from 'redux/actions/user';
|
||||||
import rewards from 'rewards';
|
import rewards from 'rewards';
|
||||||
|
@ -8,15 +9,14 @@ import {
|
||||||
selectIdentityVerifyErrorMessage,
|
selectIdentityVerifyErrorMessage,
|
||||||
} from 'redux/selectors/user';
|
} from 'redux/selectors/user';
|
||||||
import UserVerify from './view';
|
import UserVerify from './view';
|
||||||
import { doNotify, MODALS } from 'lbry-redux';
|
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = state => {
|
||||||
const selectReward = makeSelectRewardByType();
|
const selectReward = makeSelectRewardByType();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isPending: selectIdentityVerifyIsPending(state),
|
isPending: selectIdentityVerifyIsPending(state),
|
||||||
errorMessage: selectIdentityVerifyErrorMessage(state),
|
errorMessage: selectIdentityVerifyErrorMessage(state),
|
||||||
reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }),
|
reward: selectReward(state, rewards.TYPE_NEW_USER),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ class VideoPlayButton extends React.PureComponent<Props> {
|
||||||
const label = doesPlayback ? 'Play' : 'View';
|
const label = doesPlayback ? 'Play' : 'View';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button button="secondary" disabled={disabled} label={label} icon={icon} onClick={play} />
|
<Button button="primary" disabled={disabled} label={label} icon={icon} onClick={play} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
3
src/renderer/component/viewOnWebButton/index.js
Normal file
3
src/renderer/component/viewOnWebButton/index.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import ViewOnWebButton from './view';
|
||||||
|
|
||||||
|
export default ViewOnWebButton;
|
21
src/renderer/component/viewOnWebButton/view.jsx
Normal file
21
src/renderer/component/viewOnWebButton/view.jsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import * as icons from 'constants/icons';
|
||||||
|
import Button from 'component/button';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
uri: ?string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props: Props) => {
|
||||||
|
const { uri } = props;
|
||||||
|
|
||||||
|
return uri ? (
|
||||||
|
<Button
|
||||||
|
iconRight={icons.GLOBE}
|
||||||
|
button="alt"
|
||||||
|
label={__('View on Web')}
|
||||||
|
href={`http://spee.ch/${uri}`}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
};
|
|
@ -25,3 +25,4 @@ export const CHECK = 'CheckCircle';
|
||||||
export const HEART = 'Heart';
|
export const HEART = 'Heart';
|
||||||
export const UNLOCK = 'Unlock';
|
export const UNLOCK = 'Unlock';
|
||||||
export const CHECK_SIMPLE = 'Check';
|
export const CHECK_SIMPLE = 'Check';
|
||||||
|
export const GLOBE = 'Globe';
|
||||||
|
|
|
@ -8,7 +8,7 @@ import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { doConditionalAuthNavigate, doDaemonReady, doAutoUpdate } from 'redux/actions/app';
|
import { doConditionalAuthNavigate, doDaemonReady, doAutoUpdate } from 'redux/actions/app';
|
||||||
import { doNotify, doBlackListedOutpointsSubscribe } from 'lbry-redux';
|
import { doNotify, doBlackListedOutpointsSubscribe, isURIValid } from 'lbry-redux';
|
||||||
import { doNavigate } from 'redux/actions/navigation';
|
import { doNavigate } from 'redux/actions/navigation';
|
||||||
import { doDownloadLanguages, doUpdateIsNightAsync } from 'redux/actions/settings';
|
import { doDownloadLanguages, doUpdateIsNightAsync } from 'redux/actions/settings';
|
||||||
import { doUserEmailVerify } from 'redux/actions/user';
|
import { doUserEmailVerify } from 'redux/actions/user';
|
||||||
|
@ -17,8 +17,10 @@ import store from 'store';
|
||||||
import app from './app';
|
import app from './app';
|
||||||
import analytics from './analytics';
|
import analytics from './analytics';
|
||||||
import doLogWarningConsoleMessage from './logWarningConsoleMessage';
|
import doLogWarningConsoleMessage from './logWarningConsoleMessage';
|
||||||
|
import { initContextMenu } from './util/contextMenu';
|
||||||
|
|
||||||
const { autoUpdater } = remote.require('electron-updater');
|
const { autoUpdater } = remote.require('electron-updater');
|
||||||
|
const APPPAGEURL = 'lbry://?';
|
||||||
|
|
||||||
autoUpdater.logger = remote.require('electron-log');
|
autoUpdater.logger = remote.require('electron-log');
|
||||||
|
|
||||||
|
@ -42,8 +44,18 @@ ipcRenderer.on('open-uri-requested', (event, uri, newSession) => {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (uri.startsWith(APPPAGEURL)) {
|
||||||
|
const navpage = uri.replace(APPPAGEURL, '').toLowerCase();
|
||||||
|
app.store.dispatch(doNavigate(`/${navpage}`));
|
||||||
|
} else if (isURIValid(uri)) {
|
||||||
app.store.dispatch(doNavigate('/show', { uri }));
|
app.store.dispatch(doNavigate('/show', { uri }));
|
||||||
|
} else {
|
||||||
|
app.store.dispatch(
|
||||||
|
doNotify({
|
||||||
|
message: __('Invalid LBRY URL requested'),
|
||||||
|
displayType: ['snackbar'],
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -101,6 +113,8 @@ document.addEventListener('click', event => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.addEventListener('contextmenu', initContextMenu);
|
||||||
|
|
||||||
const init = () => {
|
const init = () => {
|
||||||
autoUpdater.on('update-downloaded', () => {
|
autoUpdater.on('update-downloaded', () => {
|
||||||
app.store.dispatch(doAutoUpdate());
|
app.store.dispatch(doAutoUpdate());
|
||||||
|
@ -135,7 +149,7 @@ const init = () => {
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<div>
|
<div>
|
||||||
<App />
|
<App onContextMenu={e => openContextMenu(e)} />
|
||||||
<SnackBar />
|
<SnackBar />
|
||||||
</div>
|
</div>
|
||||||
</Provider>,
|
</Provider>,
|
||||||
|
|
|
@ -25,16 +25,13 @@ const ModalCreditIntro = props => {
|
||||||
can take are limited.
|
can take are limited.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<p>
|
{totalRewardValue && (
|
||||||
There are a variety of ways to get credits, including more than{' '}
|
<p>
|
||||||
{totalRewardValue ? (
|
There are a variety of ways to get credits, including more than{' '}
|
||||||
<CreditAmount noStyle amount={totalRewardRounded} />
|
<CreditAmount noStyle amount={totalRewardRounded} />{' '}
|
||||||
) : (
|
{__('in free rewards for participating in the LBRY beta.')}
|
||||||
<span className="credit-amount">{__('?? credits')}</span>
|
</p>
|
||||||
)}{' '}
|
)}
|
||||||
{__(' in free rewards for participating in the LBRY beta.')}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="card__actions card__actions--center">
|
<div className="card__actions card__actions--center">
|
||||||
<Button button="primary" onClick={addBalance} label={__('Get Credits')} />
|
<Button button="primary" onClick={addBalance} label={__('Get Credits')} />
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -8,7 +8,7 @@ const select = state => {
|
||||||
const selectReward = makeSelectRewardByType();
|
const selectReward = makeSelectRewardByType();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
reward: selectReward(state, { reward_type: rewards.TYPE_NEW_USER }),
|
reward: selectReward(state, rewards.TYPE_NEW_USER),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import CreditAmount from 'component/common/credit-amount';
|
||||||
|
|
||||||
class ModalFirstReward extends React.PureComponent {
|
class ModalFirstReward extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const { closeModal, reward } = this.props;
|
const { closeModal } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -18,10 +18,7 @@ class ModalFirstReward extends React.PureComponent {
|
||||||
>
|
>
|
||||||
<section>
|
<section>
|
||||||
<h3 className="modal__header">{__('Your First Reward')}</h3>
|
<h3 className="modal__header">{__('Your First Reward')}</h3>
|
||||||
<p>
|
<p>{__('You just earned your first reward!')}</p>
|
||||||
{__('You just earned your first reward of')}{' '}
|
|
||||||
<CreditAmount amount={reward.reward_amount} />.
|
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
{__(
|
{__(
|
||||||
"This reward will show in your Wallet in the top right momentarily (if it hasn't already)."
|
"This reward will show in your Wallet in the top right momentarily (if it hasn't already)."
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import BusyIndicator from 'component/common/busy-indicator';
|
import BusyIndicator from 'component/common/busy-indicator';
|
||||||
|
import Button from 'component/button';
|
||||||
import { FormField, FormRow } from 'component/common/form';
|
import { FormField, FormRow } from 'component/common/form';
|
||||||
import ReactPaginate from 'react-paginate';
|
import ReactPaginate from 'react-paginate';
|
||||||
import SubscribeButton from 'component/subscribeButton';
|
import SubscribeButton from 'component/subscribeButton';
|
||||||
|
import ViewOnWebButton from 'component/viewOnWebButton';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import FileList from 'component/fileList';
|
import FileList from 'component/fileList';
|
||||||
import type { Claim } from 'types/claim';
|
import type { Claim } from 'types/claim';
|
||||||
|
import { MODALS } from 'lbry-redux';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
|
@ -19,6 +22,7 @@ type Props = {
|
||||||
fetchClaims: (string, number) => void,
|
fetchClaims: (string, number) => void,
|
||||||
fetchClaimCount: string => void,
|
fetchClaimCount: string => void,
|
||||||
navigate: (string, {}) => void,
|
navigate: (string, {}) => void,
|
||||||
|
openModal: (string, {}) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
class ChannelPage extends React.PureComponent<Props> {
|
class ChannelPage extends React.PureComponent<Props> {
|
||||||
|
@ -56,8 +60,8 @@ class ChannelPage extends React.PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { fetching, claimsInChannel, claim, page, totalPages } = this.props;
|
const { fetching, claimsInChannel, claim, page, totalPages, uri, openModal } = this.props;
|
||||||
const { name, permanent_url: permanentUrl } = claim;
|
const { name, permanent_url: permanentUrl, claim_id: claimId } = claim;
|
||||||
|
|
||||||
let contentList;
|
let contentList;
|
||||||
if (fetching) {
|
if (fetching) {
|
||||||
|
@ -76,7 +80,14 @@ class ChannelPage extends React.PureComponent<Props> {
|
||||||
<section className="card__channel-info card__channel-info--large">
|
<section className="card__channel-info card__channel-info--large">
|
||||||
<h1>{name}</h1>
|
<h1>{name}</h1>
|
||||||
<div className="card__actions card__actions--no-margin">
|
<div className="card__actions card__actions--no-margin">
|
||||||
|
<Button
|
||||||
|
button="alt"
|
||||||
|
iconRight="Send"
|
||||||
|
label={__('Enjoy this? Send a tip')}
|
||||||
|
onClick={() => openModal(MODALS.SEND_TIP, { uri })}
|
||||||
|
/>
|
||||||
<SubscribeButton uri={permanentUrl} channelName={name} />
|
<SubscribeButton uri={permanentUrl} channelName={name} />
|
||||||
|
<ViewOnWebButton uri={`${name}:${claimId}`} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section>{contentList}</section>
|
<section>{contentList}</section>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import CategoryList from 'component/common/category-list';
|
import CategoryList from 'component/categoryList';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
fetchFeaturedUris: () => void,
|
fetchFeaturedUris: () => void,
|
||||||
|
@ -27,7 +27,11 @@ class DiscoverPage extends React.PureComponent<Props> {
|
||||||
featuredUris[category].length ? (
|
featuredUris[category].length ? (
|
||||||
<CategoryList key={category} category={category} names={featuredUris[category]} />
|
<CategoryList key={category} category={category} names={featuredUris[category]} />
|
||||||
) : (
|
) : (
|
||||||
''
|
<CategoryList
|
||||||
|
key={category}
|
||||||
|
category={category.split('#')[0]}
|
||||||
|
categoryLink={category}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
{failedToLoad && <div className="empty">{__('Failed to load landing content.')}</div>}
|
{failedToLoad && <div className="empty">{__('Failed to load landing content.')}</div>}
|
||||||
|
|
|
@ -13,10 +13,13 @@ import DateTime from 'component/dateTime';
|
||||||
import * as icons from 'constants/icons';
|
import * as icons from 'constants/icons';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import SubscribeButton from 'component/subscribeButton';
|
import SubscribeButton from 'component/subscribeButton';
|
||||||
|
import ViewOnWebButton from 'component/viewOnWebButton';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import player from 'render-media';
|
import player from 'render-media';
|
||||||
import * as settings from 'constants/settings';
|
import * as settings from 'constants/settings';
|
||||||
import type { Claim } from 'types/claim';
|
import type { Claim } from 'types/claim';
|
||||||
|
import type { Subscription } from 'types/subscription';
|
||||||
|
import FileDownloadLink from 'component/fileDownloadLink';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
claim: Claim,
|
claim: Claim,
|
||||||
|
@ -39,10 +42,10 @@ type Props = {
|
||||||
openModal: ({ id: string }, { uri: string }) => void,
|
openModal: ({ id: string }, { uri: string }) => void,
|
||||||
fetchFileInfo: string => void,
|
fetchFileInfo: string => void,
|
||||||
fetchCostInfo: string => void,
|
fetchCostInfo: string => void,
|
||||||
prepareEdit: ({}) => void,
|
prepareEdit: ({}, string) => void,
|
||||||
setClientSetting: (string, boolean | string) => void,
|
setClientSetting: (string, boolean | string) => void,
|
||||||
checkSubscription: ({ channelName: string, uri: string }) => void,
|
checkSubscription: ({ channelName: string, uri: string }) => void,
|
||||||
subscriptions: Array<{}>,
|
subscriptions: Array<Subscription>,
|
||||||
};
|
};
|
||||||
|
|
||||||
class FilePage extends React.Component<Props> {
|
class FilePage extends React.Component<Props> {
|
||||||
|
@ -78,12 +81,7 @@ class FilePage extends React.Component<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
checkSubscription = (props: Props) => {
|
checkSubscription = (props: Props) => {
|
||||||
if (
|
if (props.subscriptions.find(sub => sub.channelName === props.claim.channel_name)) {
|
||||||
props.claim.value.publisherSignature &&
|
|
||||||
props.subscriptions
|
|
||||||
.map(subscription => subscription.channelName)
|
|
||||||
.indexOf(props.claim.channel_name) !== -1
|
|
||||||
) {
|
|
||||||
props.checkSubscription({
|
props.checkSubscription({
|
||||||
channelName: props.claim.channel_name,
|
channelName: props.claim.channel_name,
|
||||||
uri: buildURI(
|
uri: buildURI(
|
||||||
|
@ -112,6 +110,7 @@ class FilePage extends React.Component<Props> {
|
||||||
prepareEdit,
|
prepareEdit,
|
||||||
navigate,
|
navigate,
|
||||||
autoplay,
|
autoplay,
|
||||||
|
costInfo,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
// File info
|
// File info
|
||||||
|
@ -128,6 +127,19 @@ class FilePage extends React.Component<Props> {
|
||||||
if (channelName && channelClaimId) {
|
if (channelName && channelClaimId) {
|
||||||
subscriptionUri = buildURI({ channelName, claimId: channelClaimId }, false);
|
subscriptionUri = buildURI({ channelName, claimId: channelClaimId }, false);
|
||||||
}
|
}
|
||||||
|
const speechSharable =
|
||||||
|
costInfo &&
|
||||||
|
costInfo.cost === 0 &&
|
||||||
|
contentType &&
|
||||||
|
['video', 'image'].includes(contentType.split('/')[0]);
|
||||||
|
|
||||||
|
// We want to use the short form uri for editing
|
||||||
|
// This is what the user is used to seeing, they don't care about the claim id
|
||||||
|
// We will select the claim id before they publish
|
||||||
|
let editUri;
|
||||||
|
if (claimIsMine) {
|
||||||
|
editUri = buildURI({ channelName, contentName: claim.name });
|
||||||
|
}
|
||||||
|
|
||||||
const isPlaying = playingUri === uri && !isPaused;
|
const isPlaying = playingUri === uri && !isPaused;
|
||||||
return (
|
return (
|
||||||
|
@ -143,17 +155,12 @@ class FilePage extends React.Component<Props> {
|
||||||
) : (
|
) : (
|
||||||
<Thumbnail shouldObscure={shouldObscureThumbnail} src={thumbnail} />
|
<Thumbnail shouldObscure={shouldObscureThumbnail} src={thumbnail} />
|
||||||
)}
|
)}
|
||||||
{!isPlaying && (
|
|
||||||
<div className="card-media__internal-links">
|
|
||||||
<FileActions uri={uri} vertical />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
<div className="card__title-identity--file">
|
<div className="card__title-identity--file">
|
||||||
<h1 className="card__title card__title--file">{title}</h1>
|
<h1 className="card__title card__title--file">{title}</h1>
|
||||||
<div className="card__title-identity-icons">
|
<div className="card__title-identity-icons">
|
||||||
<FilePrice uri={normalizeURI(uri)} />
|
<FilePrice uri={normalizeURI(uri)} />
|
||||||
{isRewardContent && <Icon icon={icons.FEATURED} />}
|
{isRewardContent && <Icon iconColor="red" tooltip="bottom" icon={icons.FEATURED} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="card__subtitle card__subtitle--file">
|
<span className="card__subtitle card__subtitle--file">
|
||||||
|
@ -163,6 +170,7 @@ class FilePage extends React.Component<Props> {
|
||||||
{metadata.nsfw && <div>NSFW</div>}
|
{metadata.nsfw && <div>NSFW</div>}
|
||||||
<div className="card__channel-info">
|
<div className="card__channel-info">
|
||||||
<UriIndicator uri={uri} link />
|
<UriIndicator uri={uri} link />
|
||||||
|
|
||||||
<div className="card__actions card__actions--no-margin">
|
<div className="card__actions card__actions--no-margin">
|
||||||
{claimIsMine ? (
|
{claimIsMine ? (
|
||||||
<Button
|
<Button
|
||||||
|
@ -170,7 +178,7 @@ class FilePage extends React.Component<Props> {
|
||||||
icon={icons.EDIT}
|
icon={icons.EDIT}
|
||||||
label={__('Edit')}
|
label={__('Edit')}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
prepareEdit(claim, uri);
|
prepareEdit(claim, editUri);
|
||||||
navigate('/publish');
|
navigate('/publish');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -185,6 +193,14 @@ class FilePage extends React.Component<Props> {
|
||||||
<SubscribeButton uri={subscriptionUri} channelName={channelName} />
|
<SubscribeButton uri={subscriptionUri} channelName={channelName} />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
|
{speechSharable && (
|
||||||
|
<ViewOnWebButton
|
||||||
|
uri={buildURI({
|
||||||
|
claimId: claim.claim_id,
|
||||||
|
contentName: claim.name,
|
||||||
|
}).slice(7)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<FormRow alignRight>
|
<FormRow alignRight>
|
||||||
|
@ -199,6 +215,11 @@ class FilePage extends React.Component<Props> {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
|
<FileDownloadLink uri={uri} />
|
||||||
|
<FileActions uri={uri} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="card__content--extra-padding">
|
||||||
<FileDetails uri={uri} />
|
<FileDetails uri={uri} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -35,6 +35,7 @@ const select = (state, props) => {
|
||||||
|
|
||||||
const claimsByUri = selectClaimsByUri(state);
|
const claimsByUri = selectClaimsByUri(state);
|
||||||
const myClaims = selectMyClaims(state);
|
const myClaims = selectMyClaims(state);
|
||||||
|
const myChannels = selectMyChannelClaims(state);
|
||||||
|
|
||||||
const claimForUri = claimsByUri[uri];
|
const claimForUri = claimsByUri[uri];
|
||||||
let winningBidForClaimUri;
|
let winningBidForClaimUri;
|
||||||
|
@ -50,6 +51,7 @@ const select = (state, props) => {
|
||||||
claimForUri,
|
claimForUri,
|
||||||
winningBidForClaimUri,
|
winningBidForClaimUri,
|
||||||
myClaimForUri,
|
myClaimForUri,
|
||||||
|
myChannels,
|
||||||
costInfo: makeSelectCostInfoForUri(props.uri)(state),
|
costInfo: makeSelectCostInfoForUri(props.uri)(state),
|
||||||
balance: selectBalance(state),
|
balance: selectBalance(state),
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,8 @@ import FileTile from 'component/fileTile';
|
||||||
import FileListSearch from 'component/fileListSearch';
|
import FileListSearch from 'component/fileListSearch';
|
||||||
import ToolTip from 'component/common/tooltip';
|
import ToolTip from 'component/common/tooltip';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
|
import Icon from 'component/common/icon';
|
||||||
|
import * as icons from 'constants/icons';
|
||||||
|
|
||||||
const MODAL_ANIMATION_TIME = 250;
|
const MODAL_ANIMATION_TIME = 250;
|
||||||
|
|
||||||
|
@ -50,10 +52,11 @@ class SearchPage extends React.PureComponent<Props> {
|
||||||
<div className="file-list__header">
|
<div className="file-list__header">
|
||||||
{__('Exact URL')}
|
{__('Exact URL')}
|
||||||
<ToolTip
|
<ToolTip
|
||||||
label="?"
|
icon
|
||||||
body={__('This is the resolution of a LBRY URL and not controlled by LBRY Inc.')}
|
body={__('This is the resolution of a LBRY URL and not controlled by LBRY Inc.')}
|
||||||
className="tooltip--header"
|
>
|
||||||
/>
|
<Icon icon={icons.HELP} />
|
||||||
|
</ToolTip>
|
||||||
</div>
|
</div>
|
||||||
<FileTile fullWidth uri={normalizeURI(query)} showUri />
|
<FileTile fullWidth uri={normalizeURI(query)} showUri />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
|
@ -42,10 +42,10 @@ export function doFetchFeaturedUris() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const success = ({ Uris }) => {
|
const success = ({ Uris }) => {
|
||||||
let urisToResolve = [];
|
const urisToResolve = Object.keys(Uris).reduce(
|
||||||
Object.keys(Uris).forEach(category => {
|
(resolve, category) => [...resolve, ...Uris[category]],
|
||||||
urisToResolve = [...urisToResolve, ...Uris[category]];
|
[]
|
||||||
});
|
);
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
doResolveUris(urisToResolve),
|
doResolveUris(urisToResolve),
|
||||||
|
|
|
@ -6,7 +6,6 @@ import type {
|
||||||
UpdatePublishFormAction,
|
UpdatePublishFormAction,
|
||||||
PublishParams,
|
PublishParams,
|
||||||
} from 'redux/reducers/publish';
|
} from 'redux/reducers/publish';
|
||||||
import { CHANNEL_NEW, CHANNEL_ANONYMOUS } from 'constants/claim';
|
|
||||||
|
|
||||||
type Action = UpdatePublishFormAction | { type: ACTIONS.CLEAR_PUBLISH };
|
type Action = UpdatePublishFormAction | { type: ACTIONS.CLEAR_PUBLISH };
|
||||||
type PromiseAction = Promise<Action>;
|
type PromiseAction = Promise<Action>;
|
||||||
|
@ -86,6 +85,7 @@ export const doPublish = (params: PublishParams) => (dispatch: Dispatch, getStat
|
||||||
thumbnail,
|
thumbnail,
|
||||||
nsfw,
|
nsfw,
|
||||||
channel,
|
channel,
|
||||||
|
channelId,
|
||||||
title,
|
title,
|
||||||
contentIsFree,
|
contentIsFree,
|
||||||
price,
|
price,
|
||||||
|
@ -104,7 +104,6 @@ export const doPublish = (params: PublishParams) => (dispatch: Dispatch, getStat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const channelName = channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : channel;
|
|
||||||
const fee = contentIsFree || !price.amount ? undefined : { ...price };
|
const fee = contentIsFree || !price.amount ? undefined : { ...price };
|
||||||
|
|
||||||
const metadata = {
|
const metadata = {
|
||||||
|
@ -126,7 +125,7 @@ export const doPublish = (params: PublishParams) => (dispatch: Dispatch, getStat
|
||||||
|
|
||||||
const publishPayload = {
|
const publishPayload = {
|
||||||
name,
|
name,
|
||||||
channel_name: channelName,
|
channel_id: channelId,
|
||||||
bid,
|
bid,
|
||||||
metadata,
|
metadata,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
import Lbryio from 'lbryio';
|
import Lbryio from 'lbryio';
|
||||||
import { doNotify, MODALS } from 'lbry-redux';
|
import { doNotify, MODALS } from 'lbry-redux';
|
||||||
import { selectUnclaimedRewardsByType } from 'redux/selectors/rewards';
|
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
|
||||||
import { selectUserIsRewardApproved } from 'redux/selectors/user';
|
import { selectUserIsRewardApproved } from 'redux/selectors/user';
|
||||||
import rewards from 'rewards';
|
import rewards from 'rewards';
|
||||||
|
|
||||||
|
@ -30,8 +30,8 @@ export function doRewardList() {
|
||||||
export function doClaimRewardType(rewardType) {
|
export function doClaimRewardType(rewardType) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const rewardsByType = selectUnclaimedRewardsByType(state);
|
const unclaimedRewards = selectUnclaimedRewards(state);
|
||||||
const reward = rewardsByType[rewardType];
|
const reward = unclaimedRewards.find(ur => ur.reward_type === rewardType);
|
||||||
const userIsRewardApproved = selectUserIsRewardApproved(state);
|
const userIsRewardApproved = selectUserIsRewardApproved(state);
|
||||||
|
|
||||||
if (!reward || reward.transaction_id) {
|
if (!reward || reward.transaction_id) {
|
||||||
|
@ -84,14 +84,14 @@ export function doClaimRewardType(rewardType) {
|
||||||
export function doClaimEligiblePurchaseRewards() {
|
export function doClaimEligiblePurchaseRewards() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const rewardsByType = selectUnclaimedRewardsByType(state);
|
const unclaimedRewards = selectUnclaimedRewards(state);
|
||||||
const userIsRewardApproved = selectUserIsRewardApproved(state);
|
const userIsRewardApproved = selectUserIsRewardApproved(state);
|
||||||
|
|
||||||
if (!userIsRewardApproved || !Lbryio.enabled) {
|
if (!userIsRewardApproved || !Lbryio.enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rewardsByType[rewards.TYPE_FIRST_STREAM]) {
|
if (unclaimedRewards.find(ur => ur.reward_type === rewards.TYPE_FIRST_STREAM)) {
|
||||||
dispatch(doClaimRewardType(rewards.TYPE_FIRST_STREAM));
|
dispatch(doClaimRewardType(rewards.TYPE_FIRST_STREAM));
|
||||||
} else {
|
} else {
|
||||||
[rewards.TYPE_MANY_DOWNLOADS, rewards.TYPE_FEATURED_DOWNLOAD].forEach(type => {
|
[rewards.TYPE_MANY_DOWNLOADS, rewards.TYPE_FEATURED_DOWNLOAD].forEach(type => {
|
||||||
|
|
|
@ -18,6 +18,7 @@ type PublishState = {
|
||||||
language: string,
|
language: string,
|
||||||
tosAccepted: boolean,
|
tosAccepted: boolean,
|
||||||
channel: string,
|
channel: string,
|
||||||
|
channelId: ?string,
|
||||||
name: string,
|
name: string,
|
||||||
nameError: ?string,
|
nameError: ?string,
|
||||||
bid: number,
|
bid: number,
|
||||||
|
@ -41,6 +42,7 @@ export type UpdatePublishFormData = {
|
||||||
language?: string,
|
language?: string,
|
||||||
tosAccepted?: boolean,
|
tosAccepted?: boolean,
|
||||||
channel?: string,
|
channel?: string,
|
||||||
|
channelId?: string,
|
||||||
name?: string,
|
name?: string,
|
||||||
nameError?: string,
|
nameError?: string,
|
||||||
bid?: number,
|
bid?: number,
|
||||||
|
@ -66,6 +68,7 @@ export type PublishParams = {
|
||||||
thumbnail: ?string,
|
thumbnail: ?string,
|
||||||
nsfw: boolean,
|
nsfw: boolean,
|
||||||
channel: string,
|
channel: string,
|
||||||
|
channelId: string,
|
||||||
title: string,
|
title: string,
|
||||||
contentIsFree: boolean,
|
contentIsFree: boolean,
|
||||||
uri: string,
|
uri: string,
|
||||||
|
@ -97,6 +100,7 @@ const defaultState: PublishState = {
|
||||||
language: 'en',
|
language: 'en',
|
||||||
nsfw: false,
|
nsfw: false,
|
||||||
channel: CHANNEL_ANONYMOUS,
|
channel: CHANNEL_ANONYMOUS,
|
||||||
|
channelId: '',
|
||||||
tosAccepted: false,
|
tosAccepted: false,
|
||||||
name: '',
|
name: '',
|
||||||
nameError: undefined,
|
nameError: undefined,
|
||||||
|
|
|
@ -4,7 +4,7 @@ const reducers = {};
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
fetching: false,
|
fetching: false,
|
||||||
claimedRewardsById: {}, // id => reward
|
claimedRewardsById: {}, // id => reward
|
||||||
unclaimedRewardsByType: {},
|
unclaimedRewards: [],
|
||||||
claimPendingByType: {},
|
claimPendingByType: {},
|
||||||
claimErrorsByType: {},
|
claimErrorsByType: {},
|
||||||
};
|
};
|
||||||
|
@ -17,19 +17,19 @@ reducers[ACTIONS.FETCH_REWARDS_STARTED] = state =>
|
||||||
reducers[ACTIONS.FETCH_REWARDS_COMPLETED] = (state, action) => {
|
reducers[ACTIONS.FETCH_REWARDS_COMPLETED] = (state, action) => {
|
||||||
const { userRewards } = action.data;
|
const { userRewards } = action.data;
|
||||||
|
|
||||||
const unclaimedRewards = {};
|
const unclaimedRewards = [];
|
||||||
const claimedRewards = {};
|
const claimedRewards = {};
|
||||||
userRewards.forEach(reward => {
|
userRewards.forEach(reward => {
|
||||||
if (reward.transaction_id) {
|
if (reward.transaction_id) {
|
||||||
claimedRewards[reward.id] = reward;
|
claimedRewards[reward.id] = reward;
|
||||||
} else {
|
} else {
|
||||||
unclaimedRewards[reward.reward_type] = reward;
|
unclaimedRewards.push(reward);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
claimedRewardsById: claimedRewards,
|
claimedRewardsById: claimedRewards,
|
||||||
unclaimedRewardsByType: unclaimedRewards,
|
unclaimedRewards,
|
||||||
fetching: false,
|
fetching: false,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -62,24 +62,21 @@ reducers[ACTIONS.CLAIM_REWARD_STARTED] = (state, action) => {
|
||||||
|
|
||||||
reducers[ACTIONS.CLAIM_REWARD_SUCCESS] = (state, action) => {
|
reducers[ACTIONS.CLAIM_REWARD_SUCCESS] = (state, action) => {
|
||||||
const { reward } = action.data;
|
const { reward } = action.data;
|
||||||
|
const { unclaimedRewards } = state;
|
||||||
|
|
||||||
const unclaimedRewardsByType = Object.assign({}, state.unclaimedRewardsByType);
|
const index = unclaimedRewards.findIndex(ur => ur.reward_type === reward.reward_type);
|
||||||
const existingReward = unclaimedRewardsByType[reward.reward_type];
|
unclaimedRewards.splice(index, 1);
|
||||||
|
|
||||||
const newReward = Object.assign({}, reward, {
|
const { claimedRewardsById } = state;
|
||||||
reward_title: existingReward.reward_title,
|
claimedRewardsById[reward.id] = reward;
|
||||||
reward_description: existingReward.reward_description,
|
|
||||||
});
|
|
||||||
|
|
||||||
const claimedRewardsById = Object.assign({}, state.claimedRewardsById);
|
const newState = {
|
||||||
claimedRewardsById[reward.id] = newReward;
|
...state,
|
||||||
|
unclaimedRewards: [...unclaimedRewards],
|
||||||
|
claimedRewardsById: { ...claimedRewardsById },
|
||||||
|
};
|
||||||
|
|
||||||
const newState = Object.assign({}, state, {
|
return setClaimRewardState(newState, reward, false, '');
|
||||||
unclaimedRewardsByType,
|
|
||||||
claimedRewardsById,
|
|
||||||
});
|
|
||||||
|
|
||||||
return setClaimRewardState(newState, newReward, false, '');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
reducers[ACTIONS.CLAIM_REWARD_FAILURE] = (state, action) => {
|
reducers[ACTIONS.CLAIM_REWARD_FAILURE] = (state, action) => {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import REWARDS from 'rewards';
|
|
||||||
|
|
||||||
const selectState = state => state.rewards || {};
|
const selectState = state => state.rewards || {};
|
||||||
|
|
||||||
|
@ -26,16 +25,7 @@ export const selectClaimedRewardsByTransactionId = createSelector(selectClaimedR
|
||||||
}, {})
|
}, {})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const selectUnclaimedRewards = createSelector(
|
export const selectUnclaimedRewards = createSelector(selectState, state => state.unclaimedRewards);
|
||||||
selectUnclaimedRewardsByType,
|
|
||||||
byType =>
|
|
||||||
Object.values(byType).sort(
|
|
||||||
(a, b) =>
|
|
||||||
REWARDS.SORT_ORDER.indexOf(a.reward_type) < REWARDS.SORT_ORDER.indexOf(b.reward_type)
|
|
||||||
? -1
|
|
||||||
: 1
|
|
||||||
) || []
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectFetchingRewards = createSelector(selectState, state => !!state.fetching);
|
export const selectFetchingRewards = createSelector(selectState, state => !!state.fetching);
|
||||||
|
|
||||||
|
@ -65,7 +55,8 @@ const selectClaimRewardError = (state, props) =>
|
||||||
export const makeSelectClaimRewardError = () =>
|
export const makeSelectClaimRewardError = () =>
|
||||||
createSelector(selectClaimRewardError, errorMessage => errorMessage);
|
createSelector(selectClaimRewardError, errorMessage => errorMessage);
|
||||||
|
|
||||||
const selectRewardByType = (state, props) => selectUnclaimedRewardsByType(state)[props.reward_type];
|
const selectRewardByType = (state, rewardType) =>
|
||||||
|
selectUnclaimedRewards(state).find(reward => reward.reward_type === rewardType);
|
||||||
|
|
||||||
export const makeSelectRewardByType = () => createSelector(selectRewardByType, reward => reward);
|
export const makeSelectRewardByType = () => createSelector(selectRewardByType, reward => reward);
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,6 @@
|
||||||
import { Lbry, doNotify } from 'lbry-redux';
|
import { Lbry, doNotify } from 'lbry-redux';
|
||||||
import Lbryio from 'lbryio';
|
import Lbryio from 'lbryio';
|
||||||
|
|
||||||
function rewardMessage(type, amount) {
|
|
||||||
return {
|
|
||||||
new_developer: __('You earned %s for registering as a new developer.', amount),
|
|
||||||
new_user: __('You earned %s LBC new user reward.', amount),
|
|
||||||
verified_email: __('You earned %s LBC for verifying your email address.', amount),
|
|
||||||
new_channel: __('You earned %s LBC for creating a publisher identity.', amount),
|
|
||||||
first_stream: __('You earned %s LBC for streaming your first video.', amount),
|
|
||||||
many_downloads: __('You earned %s LBC for downloading a bunch of things.', amount),
|
|
||||||
first_publish: __('You earned %s LBC for making your first publication.', amount),
|
|
||||||
featured_download: __('You earned %s LBC for watching a featured download.', amount),
|
|
||||||
referral: __('You earned %s LBC for referring someone.', amount),
|
|
||||||
youtube_creator: __('You earned %s LBC for syncing your YouTube channel.', amount),
|
|
||||||
}[type];
|
|
||||||
}
|
|
||||||
|
|
||||||
const rewards = {};
|
const rewards = {};
|
||||||
|
|
||||||
rewards.TYPE_NEW_DEVELOPER = 'new_developer';
|
rewards.TYPE_NEW_DEVELOPER = 'new_developer';
|
||||||
|
@ -28,18 +13,6 @@ rewards.TYPE_FIRST_PUBLISH = 'first_publish';
|
||||||
rewards.TYPE_FEATURED_DOWNLOAD = 'featured_download';
|
rewards.TYPE_FEATURED_DOWNLOAD = 'featured_download';
|
||||||
rewards.TYPE_REFERRAL = 'referral';
|
rewards.TYPE_REFERRAL = 'referral';
|
||||||
rewards.YOUTUBE_CREATOR = 'youtube_creator';
|
rewards.YOUTUBE_CREATOR = 'youtube_creator';
|
||||||
rewards.SORT_ORDER = [
|
|
||||||
rewards.TYPE_NEW_USER,
|
|
||||||
rewards.TYPE_CONFIRM_EMAIL,
|
|
||||||
rewards.TYPE_FIRST_STREAM,
|
|
||||||
rewards.TYPE_FIRST_CHANNEL,
|
|
||||||
rewards.TYPE_FIRST_PUBLISH,
|
|
||||||
rewards.TYPE_FEATURED_DOWNLOAD,
|
|
||||||
rewards.TYPE_MANY_DOWNLOADS,
|
|
||||||
rewards.TYPE_REFERRAL,
|
|
||||||
rewards.TYPE_NEW_DEVELOPER,
|
|
||||||
rewards.YOUTUBE_CREATOR,
|
|
||||||
];
|
|
||||||
|
|
||||||
rewards.claimReward = type => {
|
rewards.claimReward = type => {
|
||||||
function requestReward(resolve, reject, params) {
|
function requestReward(resolve, reject, params) {
|
||||||
|
@ -48,7 +21,8 @@ rewards.claimReward = type => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Lbryio.call('reward', 'new', params, 'post').then(reward => {
|
Lbryio.call('reward', 'new', params, 'post').then(reward => {
|
||||||
const message = rewardMessage(type, reward.reward_amount);
|
const message =
|
||||||
|
reward.reward_notification || `You have claimed a ${reward.reward_amount} LBC reward.`;
|
||||||
|
|
||||||
// Display global notice
|
// Display global notice
|
||||||
const action = doNotify({
|
const action = doNotify({
|
||||||
|
|
|
@ -35,6 +35,7 @@ $large-breakpoint: 1760px;
|
||||||
--color-green-light: #effbe4;
|
--color-green-light: #effbe4;
|
||||||
--color-green-blue: #2ec1a8;
|
--color-green-blue: #2ec1a8;
|
||||||
--color-purple: #8165b0;
|
--color-purple: #8165b0;
|
||||||
|
--color-blue-grey: #203049;
|
||||||
|
|
||||||
/* Colors */
|
/* Colors */
|
||||||
--color-divider: #e3e3e3;
|
--color-divider: #e3e3e3;
|
||||||
|
@ -73,6 +74,8 @@ $large-breakpoint: 1760px;
|
||||||
--input-copyable-bg: #f6f6f6;
|
--input-copyable-bg: #f6f6f6;
|
||||||
--input-copyable-color: var(--color-grey-dark);
|
--input-copyable-color: var(--color-grey-dark);
|
||||||
--input-copyable-border: var(--color-grey);
|
--input-copyable-border: var(--color-grey);
|
||||||
|
--input-select-bg-color: var(--color-grey);
|
||||||
|
--input-select-color: var(--text-color);
|
||||||
|
|
||||||
/* input:disabled */
|
/* input:disabled */
|
||||||
--input-disabled-border-color: rgba(0, 0, 0, 0.42);
|
--input-disabled-border-color: rgba(0, 0, 0, 0.42);
|
||||||
|
@ -158,9 +161,8 @@ $large-breakpoint: 1760px;
|
||||||
--modal-btn-bg-color: var(--btn-bg-alt);
|
--modal-btn-bg-color: var(--btn-bg-alt);
|
||||||
|
|
||||||
// /* Tooltip */
|
// /* Tooltip */
|
||||||
--tooltip-width: 300px;
|
--tooltip-bg: #555;
|
||||||
--tooltip-bg: var(--color-bg);
|
--tooltip-color: var(--color-white);
|
||||||
--tooltip-color: var(--text-color);
|
|
||||||
|
|
||||||
/* Scrollbar */
|
/* Scrollbar */
|
||||||
--scrollbar-radius: 10px;
|
--scrollbar-radius: 10px;
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
border-radius: var(--card-radius);
|
border-radius: var(--card-radius);
|
||||||
overflow: auto;
|
|
||||||
user-select: text;
|
user-select: text;
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -18,7 +17,6 @@
|
||||||
|
|
||||||
.card--small {
|
.card--small {
|
||||||
width: var(--card-small-width);
|
width: var(--card-small-width);
|
||||||
overflow-x: hidden;
|
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
|
|
||||||
.card__media {
|
.card__media {
|
||||||
|
@ -87,8 +85,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
.credit-amount,
|
.credit-amount,
|
||||||
.icon {
|
.icon {
|
||||||
margin-top: $spacing-vertical * 1/3;
|
margin: $spacing-vertical * 1/3;
|
||||||
margin-left: $spacing-vertical * 2/3;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +124,11 @@
|
||||||
color: var(--card-text-color);
|
color: var(--card-text-color);
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
margin-left: $spacing-vertical * 1/3;
|
margin-top: $spacing-vertical * 1/6;
|
||||||
|
|
||||||
|
&:not(:first-of-type) {
|
||||||
|
margin: 0 $spacing-vertical * 1/3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,11 +136,6 @@
|
||||||
padding-top: $spacing-vertical * 1/3;
|
padding-top: $spacing-vertical * 1/3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card__subtitle--file-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card__subtitle--block {
|
.card__subtitle--block {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
@ -184,6 +180,10 @@
|
||||||
margin-top: $spacing-vertical * 2/3;
|
margin-top: $spacing-vertical * 2/3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card__content--extra-padding {
|
||||||
|
margin-top: $spacing-vertical;
|
||||||
|
}
|
||||||
|
|
||||||
.card__subtext-title {
|
.card__subtext-title {
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
font-size: calc(var(--font-size-subtext-multiple) * 1.5em);
|
font-size: calc(var(--font-size-subtext-multiple) * 1.5em);
|
||||||
|
@ -240,12 +240,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
.card-row is used on the discover/subscriptions page
|
.card-row is used on the discover page
|
||||||
It is a list of cards that extend past the right edge of the screen
|
It is a list of cards that extend past the right edge of the screen
|
||||||
There are left/right arrows to scroll the cards and view hidden content
|
There are left/right arrows to scroll the cards and view hidden content
|
||||||
*/
|
*/
|
||||||
.card-row {
|
.card-row {
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: var(--card-small-width);
|
min-width: var(--card-small-width);
|
||||||
|
@ -286,6 +285,18 @@
|
||||||
padding-top: $spacing-vertical * 2/3;
|
padding-top: $spacing-vertical * 2/3;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
.card {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
overflow-y: visible;
|
||||||
|
// 31 px to handle to padding between cards
|
||||||
|
width: calc((100% / 4) - 31px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:not(:first-of-type) {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.card:first-of-type {
|
.card:first-of-type {
|
||||||
margin-left: $spacing-width;
|
margin-left: $spacing-width;
|
||||||
}
|
}
|
||||||
|
@ -324,27 +335,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-row__scrollhouse {
|
|
||||||
padding-top: $spacing-vertical * 2/3;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.card {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
|
||||||
overflow: visible;
|
|
||||||
// 31 px to handle to padding between cards
|
|
||||||
width: calc((100% / 4) - 31px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card:not(:first-of-type) {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card:last-of-type {
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card__success-msg {
|
.card__success-msg {
|
||||||
border-left: 2px solid var(--success-msg-border);
|
border-left: 2px solid var(--success-msg-border);
|
||||||
color: var(--success-msg-color);
|
color: var(--success-msg-color);
|
||||||
|
|
|
@ -1,26 +1,3 @@
|
||||||
.file-download,
|
|
||||||
.file-download__overlay {
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-download {
|
.file-download {
|
||||||
position: relative;
|
font-size: 0.8em;
|
||||||
background-color: var(--color-black);
|
|
||||||
border-radius: var(--btn-radius);
|
|
||||||
color: var(--color-download);
|
|
||||||
font-size: 12px;
|
|
||||||
opacity: 0.8;
|
|
||||||
font-family: 'metropolis-medium';
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-download__overlay {
|
|
||||||
background: var(--color-download);
|
|
||||||
color: var(--color-download-overlay);
|
|
||||||
border-radius: var(--btn-radius);
|
|
||||||
position: absolute;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
z-index: 1;
|
|
||||||
top: 0px;
|
|
||||||
left: 0px;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,10 @@
|
||||||
.file-list__header {
|
.file-list__header {
|
||||||
margin-top: $spacing-vertical * 4/3;
|
margin-top: $spacing-vertical * 4/3;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-tile {
|
.file-tile {
|
||||||
|
|
|
@ -62,6 +62,10 @@
|
||||||
width: 35px;
|
width: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input.paginate-channel {
|
||||||
|
width: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
&.form-field--auto-height {
|
&.form-field--auto-height {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
@ -100,10 +104,23 @@
|
||||||
padding-left: $spacing-vertical * 1/3;
|
padding-left: $spacing-vertical * 1/3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-field__select {
|
||||||
|
min-width: 60px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--input-select-bg-color);
|
||||||
|
font: normal 12px/30px 'metropolis-medium';
|
||||||
|
color: var(--input-select-color);
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Not sure if I like these
|
// Not sure if I like these
|
||||||
// Maybe this should be in gui.scss?
|
// Maybe this should be in gui.scss?
|
||||||
.input--price-amount {
|
.input--price-amount {
|
||||||
width: 60px;
|
width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input--address {
|
.input--address {
|
||||||
|
|
|
@ -1,30 +1,97 @@
|
||||||
@import '../mixin/link.scss';
|
|
||||||
|
|
||||||
.tooltip {
|
.tooltip {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0 $spacing-vertical / 3;
|
display: inline-block;
|
||||||
font-size: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip__body {
|
// When there is a label for the tooltip and not just using a button or icon
|
||||||
|
.tooltip.tooltip--label {
|
||||||
|
font-size: 12px;
|
||||||
|
padding-left: $spacing-vertical * 1/3;
|
||||||
|
|
||||||
|
.tooltip__body {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip.tooltip--icon {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tooltip text */
|
||||||
|
.tooltip .tooltip__body {
|
||||||
|
background-color: var(--tooltip-bg);
|
||||||
|
font-family: 'metropolis-medium';
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--tooltip-color);
|
||||||
|
border-radius: 8px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
left: 50%;
|
width: 200px;
|
||||||
margin-left: calc(var(--tooltip-width) * -1 / 2);
|
text-align: center;
|
||||||
white-space: normal;
|
white-space: pre-wrap;
|
||||||
box-sizing: border-box;
|
padding: $spacing-vertical * 1/3;
|
||||||
padding: $spacing-vertical / 2;
|
visibility: hidden;
|
||||||
width: var(--tooltip-width);
|
|
||||||
color: var(--tooltip-color);
|
|
||||||
background-color: var(--tooltip-bg);
|
|
||||||
font-size: calc(var(--font-size) * 7 / 8);
|
|
||||||
line-height: var(--font-line-height);
|
|
||||||
box-shadow: var(--box-shadow-layer);
|
|
||||||
border-radius: var(--card-radius);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip__link {
|
.tooltip .tooltip__body::after {
|
||||||
font-size: calc(var(--font-size) * 3 / 4);
|
content: ' ';
|
||||||
margin-left: var(--button-padding);
|
width: 0;
|
||||||
vertical-align: middle;
|
height: 0;
|
||||||
|
position: absolute;
|
||||||
|
border-width: 5px;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip.tooltip--top .tooltip__body {
|
||||||
|
bottom: 100%;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -100px;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
top: 100%;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -5px;
|
||||||
|
border-color: var(--tooltip-bg) transparent transparent transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip.tooltip--right .tooltip__body {
|
||||||
|
margin-top: -5px;
|
||||||
|
margin-left: 10px;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
top: 17px;
|
||||||
|
right: 100%; /* To the left of the tooltip */
|
||||||
|
margin-top: -5px;
|
||||||
|
border-color: transparent var(--tooltip-bg) transparent transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip.tooltip--bottom .tooltip__body {
|
||||||
|
top: 90%;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -100px;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
bottom: 100%;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -5px;
|
||||||
|
border-color: transparent transparent var(--tooltip-bg) transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip.tooltip--left .tooltip__body {
|
||||||
|
top: -5px;
|
||||||
|
right: 105%;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
top: 17px;
|
||||||
|
left: 100%;
|
||||||
|
margin-top: -5px;
|
||||||
|
border-color: transparent transparent transparent var(--tooltip-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip:hover .tooltip__body {
|
||||||
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
@mixin text-link($color: var(--color-primary), $hover-opacity: 0.7) {
|
|
||||||
.icon {
|
|
||||||
&:first-child {
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
&:last-child:not(:only-child) {
|
|
||||||
padding-left: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.no-underline) {
|
|
||||||
text-decoration: underline;
|
|
||||||
.icon {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
opacity: $hover-opacity;
|
|
||||||
transition: opacity var(--transition-duration) var(--transition-type);
|
|
||||||
text-decoration: underline;
|
|
||||||
.icon {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
color: $color;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
68
src/renderer/util/contextMenu.js
Normal file
68
src/renderer/util/contextMenu.js
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import { clipboard, remote } from 'electron';
|
||||||
|
import isDev from 'electron-is-dev';
|
||||||
|
|
||||||
|
function injectDevelopmentTemplate(event, templates) {
|
||||||
|
if (!isDev) return templates;
|
||||||
|
const { screenX, screenY } = event;
|
||||||
|
const separator = { type: 'separator' };
|
||||||
|
const developmentTemplateAddition = [
|
||||||
|
{
|
||||||
|
label: 'Inspect element',
|
||||||
|
accelerator: 'CmdOrCtrl+Shift+I',
|
||||||
|
click: () => {
|
||||||
|
remote.getCurrentWindow().inspectElement(screenX, screenY);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
if (templates.length > 0) {
|
||||||
|
templates.push(separator);
|
||||||
|
}
|
||||||
|
templates.push(...developmentTemplateAddition);
|
||||||
|
return templates;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openContextMenu(event, templates = [], addDefaultTemplates = true) {
|
||||||
|
if (addDefaultTemplates) {
|
||||||
|
const { value } = event.target;
|
||||||
|
const inputTemplates = [
|
||||||
|
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' },
|
||||||
|
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' },
|
||||||
|
{
|
||||||
|
label: 'Paste',
|
||||||
|
accelerator: 'CmdOrCtrl+V',
|
||||||
|
role: 'paste',
|
||||||
|
enabled: clipboard.readText().length > 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Select All',
|
||||||
|
accelerator: 'CmdOrCtrl+A',
|
||||||
|
role: 'selectall',
|
||||||
|
enabled: !!value,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
templates.push(...inputTemplates);
|
||||||
|
}
|
||||||
|
|
||||||
|
injectDevelopmentTemplate(event, templates);
|
||||||
|
remote.Menu.buildFromTemplate(templates).popup();
|
||||||
|
}
|
||||||
|
export function openCopyLinkMenu(text, event) {
|
||||||
|
const templates = [
|
||||||
|
{
|
||||||
|
label: 'Copy link',
|
||||||
|
click: () => {
|
||||||
|
clipboard.writeText(text);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
openContextMenu(event, templates, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initContextMenu(event) {
|
||||||
|
const { type } = event.target;
|
||||||
|
if (event.target.matches('input') && (type === 'text' || type === 'number')) {
|
||||||
|
openContextMenu(event);
|
||||||
|
} else {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
--color-help: #8696AF;
|
--color-help: #8696AF;
|
||||||
--color-download: rgba(255, 255, 255, 0.75);
|
--color-download: rgba(255, 255, 255, 0.75);
|
||||||
--color-download-overlay: var(--color-black);
|
--color-download-overlay: var(--color-black);
|
||||||
--color-bg: #203049;
|
--color-bg: var(--color-blue-grey);
|
||||||
--color-bg-alt: #2D3D56;
|
--color-bg-alt: #2D3D56;
|
||||||
--color-placeholder: var(--color-bg-alt);
|
--color-placeholder: var(--color-bg-alt);
|
||||||
|
|
||||||
|
@ -26,9 +26,11 @@
|
||||||
--input-border-size: 1px;
|
--input-border-size: 1px;
|
||||||
--input-border-color: rgba(255,255,255, 0.5);
|
--input-border-color: rgba(255,255,255, 0.5);
|
||||||
--input-hover-border-color: rgba(255, 255, 255, 1);
|
--input-hover-border-color: rgba(255, 255, 255, 1);
|
||||||
--input-copyable-bg: #203049;
|
--input-copyable-bg: var(--color-blue-grey);
|
||||||
--input-copyable-color: #8696AF;
|
--input-copyable-color: #8696AF;
|
||||||
--input-copyable-border: #53637C;
|
--input-copyable-border: #53637C;
|
||||||
|
--input-select-bg-color: var(--color-bg-alt);
|
||||||
|
--input-select-color: var(--color-white);
|
||||||
|
|
||||||
/* input:disabled */
|
/* input:disabled */
|
||||||
--input-disabled-border-color: rgba(255, 255, 255, 0.42);
|
--input-disabled-border-color: rgba(255, 255, 255, 0.42);
|
||||||
|
|
|
@ -5837,9 +5837,9 @@ lazy-val@^1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.3.tgz#bb97b200ef00801d94c317e29dc6ed39e31c5edc"
|
resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.3.tgz#bb97b200ef00801d94c317e29dc6ed39e31c5edc"
|
||||||
|
|
||||||
lbry-redux@lbryio/lbry-redux#30c18725d8c6c141c30c57f0a324d0abb8963b99:
|
lbry-redux@lbryio/lbry-redux#c41899e78415cae6fcb7bfca0e6ba48bb6bfe6c4:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/30c18725d8c6c141c30c57f0a324d0abb8963b99"
|
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/c41899e78415cae6fcb7bfca0e6ba48bb6bfe6c4"
|
||||||
dependencies:
|
dependencies:
|
||||||
proxy-polyfill "0.1.6"
|
proxy-polyfill "0.1.6"
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
|
|
Loading…
Add table
Reference in a new issue