Random fixes #2729
32 changed files with 249 additions and 101 deletions
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "LBRY",
|
"name": "LBRY",
|
||||||
"version": "0.34.2",
|
"version": "0.35.0-rc.1",
|
||||||
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
|
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"lbry"
|
"lbry"
|
||||||
|
@ -125,8 +125,8 @@
|
||||||
"jsmediatags": "^3.8.1",
|
"jsmediatags": "^3.8.1",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||||
"lbry-redux": "lbryio/lbry-redux#05e70648e05c51c51710f6dd698a8e2219b54df2",
|
"lbry-redux": "lbryio/lbry-redux#6005fa245a888e2de045d7e42411847de7943f52",
|
||||||
"lbryinc": "lbryio/lbryinc#a93596c51c8fb0a226cb84df04c26a6bb60a45fb",
|
"lbryinc": "lbryio/lbryinc#1ce266b3c52654190b955e9c869b8e302aa5c585",
|
||||||
"lint-staged": "^7.0.2",
|
"lint-staged": "^7.0.2",
|
||||||
"localforage": "^1.7.1",
|
"localforage": "^1.7.1",
|
||||||
"lodash-es": "^4.17.14",
|
"lodash-es": "^4.17.14",
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Lbryio } from 'lbryinc';
|
||||||
import ReactGA from 'react-ga';
|
import ReactGA from 'react-ga';
|
||||||
import { history } from './store';
|
import { history } from './store';
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
|
import Native from 'native';
|
||||||
import ElectronCookies from '@exponent/electron-cookies';
|
import ElectronCookies from '@exponent/electron-cookies';
|
||||||
// @endif
|
// @endif
|
||||||
|
|
||||||
|
@ -15,6 +16,9 @@ type Analytics = {
|
||||||
apiLogView: (string, string, string, ?number, ?() => void) => void,
|
apiLogView: (string, string, string, ?number, ?() => void) => void,
|
||||||
apiLogPublish: () => void,
|
apiLogPublish: () => void,
|
||||||
tagFollowEvent: (string, boolean, string) => void,
|
tagFollowEvent: (string, boolean, string) => void,
|
||||||
|
emailProvidedEvent: () => void,
|
||||||
|
emailVerifiedEvent: () => void,
|
||||||
|
rewardEligibleEvent: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
let analyticsEnabled: boolean = true;
|
let analyticsEnabled: boolean = true;
|
||||||
|
@ -26,11 +30,15 @@ const analytics: Analytics = {
|
||||||
},
|
},
|
||||||
setUser: userId => {
|
setUser: userId => {
|
||||||
if (analyticsEnabled && userId) {
|
if (analyticsEnabled && userId) {
|
||||||
ReactGA.event({
|
ReactGA.set({
|
||||||
category: 'User',
|
userId,
|
||||||
action: 'userId',
|
|
||||||
value: userId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// @if TARGET='app'
|
||||||
|
Native.getAppVersionInfo().then(({ localVersion }) => {
|
||||||
|
sendGaEvent('Desktop-Version', localVersion);
|
||||||
|
});
|
||||||
|
// @endif
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
toggle: (enabled: boolean): void => {
|
toggle: (enabled: boolean): void => {
|
||||||
|
@ -39,8 +47,8 @@ const analytics: Analytics = {
|
||||||
analyticsEnabled = enabled;
|
analyticsEnabled = enabled;
|
||||||
// @endif
|
// @endif
|
||||||
},
|
},
|
||||||
apiLogView: (uri, outpoint, claimId, timeToStart, onSuccessCb) => {
|
apiLogView: (uri, outpoint, claimId, timeToStart) => {
|
||||||
if (analyticsEnabled) {
|
if (analyticsEnabled && isProduction) {
|
||||||
const params: {
|
const params: {
|
||||||
uri: string,
|
uri: string,
|
||||||
outpoint: string,
|
outpoint: string,
|
||||||
|
@ -52,17 +60,12 @@ const analytics: Analytics = {
|
||||||
claim_id: claimId,
|
claim_id: claimId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (timeToStart) {
|
// lbry.tv streams from AWS so we don't care about the time to start
|
||||||
|
if (timeToStart && !IS_WEB) {
|
||||||
params.time_to_start = timeToStart;
|
params.time_to_start = timeToStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
Lbryio.call('file', 'view', params)
|
Lbryio.call('file', 'view', params);
|
||||||
.then(() => {
|
|
||||||
if (onSuccessCb) {
|
|
||||||
onSuccessCb();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
apiLogSearch: () => {
|
apiLogSearch: () => {
|
||||||
|
@ -82,23 +85,31 @@ const analytics: Analytics = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tagFollowEvent: (tag, following, location) => {
|
tagFollowEvent: (tag, following, location) => {
|
||||||
if (analyticsEnabled) {
|
sendGaEvent(following ? 'Tag-Follow' : 'Tag-Unfollow', tag);
|
||||||
ReactGA.event({
|
|
||||||
category: following ? 'Tag-Follow' : 'Tag-Unfollow',
|
|
||||||
action: tag,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
channelBlockEvent: (uri, blocked, location) => {
|
channelBlockEvent: (uri, blocked, location) => {
|
||||||
if (analyticsEnabled) {
|
sendGaEvent(blocked ? 'Channel-Hidden' : 'Channel-Unhidden', uri);
|
||||||
ReactGA.event({
|
},
|
||||||
category: blocked ? 'Channel-Hidden' : 'Channel-Unhidden',
|
emailProvidedEvent: () => {
|
||||||
action: uri,
|
sendGaEvent('Engagement', 'Email-Provided');
|
||||||
});
|
},
|
||||||
}
|
emailVerifiedEvent: () => {
|
||||||
|
sendGaEvent('Engagement', 'Email-Verified');
|
||||||
|
},
|
||||||
|
rewardEligibleEvent: () => {
|
||||||
|
sendGaEvent('Engagement', 'Reward-Eligible');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
Yeah I've been meaning to do this, just did and it's way nicer now. Yeah I've been meaning to do this, just did and it's way nicer now.
|
|||||||
|
|
||||||
|
function sendGaEvent(category, action) {
|
||||||
|
if (analyticsEnabled && isProduction) {
|
||||||
|
ReactGA.event({
|
||||||
|
category,
|
||||||
|
action,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize google analytics
|
// Initialize google analytics
|
||||||
// Set `debug: true` for debug info
|
// Set `debug: true` for debug info
|
||||||
// Will change once we have separate ids for desktop/web
|
// Will change once we have separate ids for desktop/web
|
||||||
|
@ -113,6 +124,8 @@ ElectronCookies.enable({
|
||||||
ReactGA.initialize(UA_ID, {
|
ReactGA.initialize(UA_ID, {
|
||||||
testMode: process.env.NODE_ENV !== 'production',
|
testMode: process.env.NODE_ENV !== 'production',
|
||||||
cookieDomain: 'auto',
|
cookieDomain: 'auto',
|
||||||
|
// un-comment to see events as they are sent to google
|
||||||
|
// debug: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Manually call the first page view
|
// Manually call the first page view
|
||||||
|
|
|
@ -12,6 +12,7 @@ import useKonamiListener from 'util/enhanced-layout';
|
||||||
import Yrbl from 'component/yrbl';
|
import Yrbl from 'component/yrbl';
|
||||||
import FileViewer from 'component/fileViewer';
|
import FileViewer from 'component/fileViewer';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
|
import usePrevious from 'util/use-previous';
|
||||||
|
|
||||||
export const MAIN_WRAPPER_CLASS = 'main-wrapper';
|
export const MAIN_WRAPPER_CLASS = 'main-wrapper';
|
||||||
|
|
||||||
|
@ -21,7 +22,7 @@ type Props = {
|
||||||
language: string,
|
language: string,
|
||||||
theme: string,
|
theme: string,
|
||||||
accessToken: ?string,
|
accessToken: ?string,
|
||||||
user: ?{ id: string, has_verified_email: boolean },
|
user: ?{ id: string, has_verified_email: boolean, is_reward_approved: boolean },
|
||||||
location: { pathname: string },
|
location: { pathname: string },
|
||||||
fetchRewards: () => void,
|
fetchRewards: () => void,
|
||||||
fetchRewardedContent: () => void,
|
fetchRewardedContent: () => void,
|
||||||
|
@ -35,7 +36,10 @@ function App(props: Props) {
|
||||||
const isEnhancedLayout = useKonamiListener();
|
const isEnhancedLayout = useKonamiListener();
|
||||||
const userId = user && user.id;
|
const userId = user && user.id;
|
||||||
const hasVerifiedEmail = user && user.has_verified_email;
|
const hasVerifiedEmail = user && user.has_verified_email;
|
||||||
|
const isRewardApproved = user && user.is_reward_approved;
|
||||||
|
const previousUserId = usePrevious(userId);
|
||||||
|
const previousHasVerifiedEmail = usePrevious(hasVerifiedEmail);
|
||||||
|
const previousRewardApproved = usePrevious(isRewardApproved);
|
||||||
const { pathname } = props.location;
|
const { pathname } = props.location;
|
||||||
const urlParts = pathname.split('/');
|
const urlParts = pathname.split('/');
|
||||||
const claimName = urlParts[1];
|
const claimName = urlParts[1];
|
||||||
|
@ -65,10 +69,24 @@ function App(props: Props) {
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userId) {
|
if (previousUserId === undefined && userId) {
|
||||||
analytics.setUser(userId);
|
analytics.setUser(userId);
|
||||||
}
|
}
|
||||||
}, [userId]);
|
}, [previousUserId, userId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Check that previousHasVerifiedEmail was not undefined instead of just not truthy
|
||||||
|
// This ensures we don't fire the emailVerified event on the initial user fetch
|
||||||
|
if (previousHasVerifiedEmail !== undefined && hasVerifiedEmail) {
|
||||||
|
analytics.emailVerifiedEvent();
|
||||||
|
}
|
||||||
|
}, [previousHasVerifiedEmail, hasVerifiedEmail]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (previousRewardApproved !== undefined && isRewardApproved) {
|
||||||
|
analytics.rewardEligibleEvent();
|
||||||
|
}
|
||||||
|
}, [previousRewardApproved, isRewardApproved]);
|
||||||
|
|
||||||
// @if TARGET='web'
|
// @if TARGET='web'
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
doResolveUri,
|
doResolveUri,
|
||||||
|
@ -10,11 +11,15 @@ import {
|
||||||
makeSelectClaimIsNsfw,
|
makeSelectClaimIsNsfw,
|
||||||
selectBlockedChannels,
|
selectBlockedChannels,
|
||||||
selectChannelIsBlocked,
|
selectChannelIsBlocked,
|
||||||
|
doClearPublish,
|
||||||
|
doPrepareEdit,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
||||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||||
import { makeSelectHasVisitedUri } from 'redux/selectors/content';
|
import { makeSelectHasVisitedUri } from 'redux/selectors/content';
|
||||||
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||||
|
import { push } from 'connected-react-router';
|
||||||
|
|
||||||
import ClaimPreview from './view';
|
import ClaimPreview from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
|
@ -36,6 +41,11 @@ const select = (state, props) => ({
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
|
beginPublish: name => {
|
||||||
|
dispatch(doClearPublish());
|
||||||
|
dispatch(doPrepareEdit({ name }));
|
||||||
|
dispatch(push(`/$/${PAGES.PUBLISH}`));
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|
|
@ -44,6 +44,7 @@ type Props = {
|
||||||
blockedChannelUris: Array<string>,
|
blockedChannelUris: Array<string>,
|
||||||
channelIsBlocked: boolean,
|
channelIsBlocked: boolean,
|
||||||
isSubscribed: boolean,
|
isSubscribed: boolean,
|
||||||
|
beginPublish: string => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
|
@ -68,6 +69,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
showUserBlocked,
|
showUserBlocked,
|
||||||
channelIsBlocked,
|
channelIsBlocked,
|
||||||
isSubscribed,
|
isSubscribed,
|
||||||
|
beginPublish,
|
||||||
} = props;
|
} = props;
|
||||||
const haventFetched = claim === undefined;
|
const haventFetched = claim === undefined;
|
||||||
const abandoned = !isResolvingUri && !claim;
|
const abandoned = !isResolvingUri && !claim;
|
||||||
|
@ -77,8 +79,9 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
const hideActions = type === 'small' || type === 'tooltip';
|
const hideActions = type === 'small' || type === 'tooltip';
|
||||||
|
|
||||||
let isValid;
|
let isValid;
|
||||||
|
let name;
|
||||||
try {
|
try {
|
||||||
parseURI(uri);
|
({ claimName: name } = parseURI(uri));
|
||||||
isValid = true;
|
isValid = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
isValid = false;
|
isValid = false;
|
||||||
|
@ -191,7 +194,11 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div>{__('Publish something and claim this spot!')}</div>
|
<div>{__('Publish something and claim this spot!')}</div>
|
||||||
<div className="card__actions">
|
<div className="card__actions">
|
||||||
<Button button="primary" label={`${__('Publish to')} ${uri}`} />
|
<Button
|
||||||
|
onClick={() => beginPublish(name)}
|
||||||
|
button="primary"
|
||||||
|
label={`${__('Publish to')} ${uri}`}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -25,7 +25,7 @@ class IconComponent extends React.PureComponent<Props> {
|
||||||
case ICONS.FEATURED:
|
case ICONS.FEATURED:
|
||||||
return __('Featured content. Earn rewards for watching.');
|
return __('Featured content. Earn rewards for watching.');
|
||||||
case ICONS.DOWNLOAD:
|
case ICONS.DOWNLOAD:
|
||||||
return __('This file is downloaded.');
|
return __('This file is in your library.');
|
||||||
case ICONS.SUBSCRIBE:
|
case ICONS.SUBSCRIBE:
|
||||||
return __('You are subscribed to this channel.');
|
return __('You are subscribed to this channel.');
|
||||||
case ICONS.SETTINGS:
|
case ICONS.SETTINGS:
|
||||||
|
|
|
@ -16,9 +16,7 @@ type Props = {
|
||||||
class FileActions extends React.PureComponent<Props> {
|
class FileActions extends React.PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
const { fileInfo, uri, openModal, claimIsMine, claimId } = this.props;
|
const { fileInfo, uri, openModal, claimIsMine, claimId } = this.props;
|
||||||
const showDelete =
|
const showDelete = claimIsMine || (fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed > 0));
|
||||||
claimIsMine ||
|
|
||||||
(fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed === fileInfo.blobs_in_stream));
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{showDelete && (
|
{showDelete && (
|
||||||
|
|
|
@ -77,6 +77,7 @@ class FileDetails extends PureComponent<Props> {
|
||||||
{': '}
|
{': '}
|
||||||
<Button
|
<Button
|
||||||
button="link"
|
button="link"
|
||||||
|
className="button--download-link"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (downloadPath) {
|
if (downloadPath) {
|
||||||
openFolder(downloadPath);
|
openFolder(downloadPath);
|
||||||
|
|
|
@ -20,12 +20,10 @@ type Props = {
|
||||||
function FileDownloadLink(props: Props) {
|
function FileDownloadLink(props: Props) {
|
||||||
const { fileInfo, downloading, loading, openModal, pause, claimIsMine, download, uri } = props;
|
const { fileInfo, downloading, loading, openModal, pause, claimIsMine, download, uri } = props;
|
||||||
|
|
||||||
if (loading || downloading) {
|
if (downloading || loading) {
|
||||||
const progress = fileInfo && fileInfo.written_bytes > 0 ? (fileInfo.written_bytes / fileInfo.total_bytes) * 100 : 0;
|
const progress = fileInfo && fileInfo.written_bytes > 0 ? (fileInfo.written_bytes / fileInfo.total_bytes) * 100 : 0;
|
||||||
const label =
|
const label =
|
||||||
fileInfo && fileInfo.written_bytes > 0
|
fileInfo && fileInfo.written_bytes > 0 ? progress.toFixed(0) + __('% downloaded') : __('Connecting...');
|
||||||
? __('Downloading: ') + progress.toFixed(0) + __('% complete')
|
|
||||||
: __('Connecting...');
|
|
||||||
|
|
||||||
return <span>{label}</span>;
|
return <span>{label}</span>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeSelectFileInfoForUri, makeSelectClaimIsMine } from 'lbry-redux';
|
import { makeSelectFilePartlyDownloaded, makeSelectClaimIsMine } from 'lbry-redux';
|
||||||
import { selectRewardContentClaimIds } from 'lbryinc';
|
import { selectRewardContentClaimIds } from 'lbryinc';
|
||||||
import { makeSelectIsSubscribed, makeSelectIsNew } from 'redux/selectors/subscriptions';
|
import { makeSelectIsSubscribed, makeSelectIsNew } from 'redux/selectors/subscriptions';
|
||||||
import FileProperties from './view';
|
import FileProperties from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
|
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
|
||||||
downloaded: !!makeSelectFileInfoForUri(props.uri)(state),
|
downloaded: makeSelectFilePartlyDownloaded(props.uri)(state),
|
||||||
isSubscribed: makeSelectIsSubscribed(props.uri)(state),
|
isSubscribed: makeSelectIsSubscribed(props.uri)(state),
|
||||||
isNew: makeSelectIsNew(props.uri)(state),
|
isNew: makeSelectIsNew(props.uri)(state),
|
||||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { makeSelectIsPlaying, makeSelectShouldObscurePreview, selectPlayingUri }
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import { doSetPlayingUri } from 'redux/actions/content';
|
import { doSetPlayingUri } from 'redux/actions/content';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
|
import { doAnalyticsView } from 'redux/actions/app';
|
||||||
import FileViewer from './view';
|
import FileViewer from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
|
@ -32,6 +33,7 @@ const select = (state, props) => {
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
clearPlayingUri: () => dispatch(doSetPlayingUri(null)),
|
clearPlayingUri: () => dispatch(doSetPlayingUri(null)),
|
||||||
|
triggerAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default withRouter(
|
export default withRouter(
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import LoadingScreen from 'component/common/loading-screen';
|
import LoadingScreen from 'component/common/loading-screen';
|
||||||
import FileRender from 'component/fileRender';
|
import FileRender from 'component/fileRender';
|
||||||
import UriIndicator from 'component/uriIndicator';
|
import UriIndicator from 'component/uriIndicator';
|
||||||
import usePersistedState from 'util/use-persisted-state';
|
import usePersistedState from 'util/use-persisted-state';
|
||||||
|
import usePrevious from 'util/use-previous';
|
||||||
import { FILE_WRAPPER_CLASS } from 'page/file/view';
|
import { FILE_WRAPPER_CLASS } from 'page/file/view';
|
||||||
import Draggable from 'react-draggable';
|
import Draggable from 'react-draggable';
|
||||||
import Tooltip from 'component/common/tooltip';
|
import Tooltip from 'component/common/tooltip';
|
||||||
|
@ -27,6 +28,7 @@ type Props = {
|
||||||
title: ?string,
|
title: ?string,
|
||||||
floatingPlayerEnabled: boolean,
|
floatingPlayerEnabled: boolean,
|
||||||
clearPlayingUri: () => void,
|
clearPlayingUri: () => void,
|
||||||
|
triggerAnalyticsView: (string, number) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function FileViewer(props: Props) {
|
export default function FileViewer(props: Props) {
|
||||||
|
@ -40,7 +42,9 @@ export default function FileViewer(props: Props) {
|
||||||
title,
|
title,
|
||||||
clearPlayingUri,
|
clearPlayingUri,
|
||||||
floatingPlayerEnabled,
|
floatingPlayerEnabled,
|
||||||
|
triggerAnalyticsView,
|
||||||
} = props;
|
} = props;
|
||||||
|
const [playTime, setPlayTime] = useState();
|
||||||
const [fileViewerRect, setFileViewerRect] = usePersistedState('inline-file-viewer:rect');
|
const [fileViewerRect, setFileViewerRect] = usePersistedState('inline-file-viewer:rect');
|
||||||
const [position, setPosition] = usePersistedState('floating-file-viewer:position', {
|
const [position, setPosition] = usePersistedState('floating-file-viewer:position', {
|
||||||
x: -25,
|
x: -25,
|
||||||
|
@ -54,15 +58,24 @@ export default function FileViewer(props: Props) {
|
||||||
? __("It looks like you deleted or moved this file. We're rebuilding it now. It will only take a few seconds.")
|
? __("It looks like you deleted or moved this file. We're rebuilding it now. It will only take a few seconds.")
|
||||||
: __('Loading');
|
: __('Loading');
|
||||||
|
|
||||||
function handleDrag(e, ui) {
|
const previousUri = usePrevious(uri);
|
||||||
const { x, y } = position;
|
const previousIsReadyToPlay = usePrevious(isReadyToPlay);
|
||||||
const newX = x + ui.deltaX;
|
const isNewView = uri && previousUri !== uri && isPlaying;
|
||||||
const newY = y + ui.deltaY;
|
const wasntReadyButNowItIs = isReadyToPlay && !previousIsReadyToPlay;
|
||||||
setPosition({
|
|
||||||
x: newX,
|
useEffect(() => {
|
||||||
y: newY,
|
if (isNewView) {
|
||||||
});
|
setPlayTime(Date.now());
|
||||||
}
|
}
|
||||||
|
}, [isNewView, uri]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (playTime && isReadyToPlay && wasntReadyButNowItIs) {
|
||||||
|
const timeToStart = Date.now() - playTime;
|
||||||
|
triggerAnalyticsView(uri, timeToStart);
|
||||||
|
setPlayTime(null);
|
||||||
|
}
|
||||||
|
}, [setPlayTime, triggerAnalyticsView, isReadyToPlay, wasntReadyButNowItIs, playTime, uri]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleResize() {
|
function handleResize() {
|
||||||
|
@ -85,9 +98,18 @@ export default function FileViewer(props: Props) {
|
||||||
}
|
}
|
||||||
}, [setFileViewerRect, inline]);
|
}, [setFileViewerRect, inline]);
|
||||||
|
|
||||||
|
function handleDrag(e, ui) {
|
||||||
|
const { x, y } = position;
|
||||||
|
const newX = x + ui.deltaX;
|
||||||
|
const newY = y + ui.deltaY;
|
||||||
|
setPosition({
|
||||||
|
x: newX,
|
||||||
😂 :joy:
|
|||||||
|
y: newY,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const hidePlayer = !isPlaying || !uri || (!inline && (!floatingPlayerEnabled || !isStreamable));
|
const hidePlayer = !isPlaying || !uri || (!inline && (!floatingPlayerEnabled || !isStreamable));
|
||||||
@tzarebczan brought up that we might not want to do play time (or keep play time separate) for non-streamable content. @tzarebczan brought up that we might not want to do play time (or keep play time separate) for non-streamable content.
I'm not particularly concerned about this, I think it can be cleaned up in internal analytics. I'm not particularly concerned about this, I think it can be cleaned up in internal analytics.
|
|||||||
if (hidePlayer) {
|
if (hidePlayer) {
|
||||||
clearPlayingUri();
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,18 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function FileViewer(props: Props) {
|
export default function FileViewer(props: Props) {
|
||||||
const { play, mediaType, isPlaying, fileInfo, uri, obscurePreview, insufficientCredits, thumbnail, autoplay } = props;
|
const {
|
||||||
|
play,
|
||||||
|
mediaType,
|
||||||
|
isPlaying,
|
||||||
|
fileInfo,
|
||||||
|
uri,
|
||||||
|
obscurePreview,
|
||||||
|
insufficientCredits,
|
||||||
|
thumbnail,
|
||||||
|
autoplay,
|
||||||
|
isStreamable,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const isPlayable = ['audio', 'video'].indexOf(mediaType) !== -1;
|
const isPlayable = ['audio', 'video'].indexOf(mediaType) !== -1;
|
||||||
const fileStatus = fileInfo && fileInfo.status;
|
const fileStatus = fileInfo && fileInfo.status;
|
||||||
|
@ -64,10 +75,10 @@ export default function FileViewer(props: Props) {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const videoOnPage = document.querySelector('video');
|
const videoOnPage = document.querySelector('video');
|
||||||
if (autoplay && !videoOnPage) {
|
if (autoplay && !videoOnPage && isStreamable) {
|
||||||
viewFile();
|
viewFile();
|
||||||
}
|
}
|
||||||
}, [autoplay, viewFile]);
|
}, [autoplay, viewFile, isStreamable]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -3,6 +3,7 @@ import * as React from 'react';
|
||||||
import { FormField, Form } from 'component/common/form';
|
import { FormField, Form } from 'component/common/form';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import { Lbryio } from 'lbryinc';
|
import { Lbryio } from 'lbryinc';
|
||||||
|
import analytics from 'analytics';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
cancelButton: React.Node,
|
cancelButton: React.Node,
|
||||||
|
@ -37,6 +38,7 @@ class UserEmailNew extends React.PureComponent<Props, State> {
|
||||||
const { email } = this.state;
|
const { email } = this.state;
|
||||||
const { addUserEmail } = this.props;
|
const { addUserEmail } = this.props;
|
||||||
addUserEmail(email);
|
addUserEmail(email);
|
||||||
|
analytics.emailProvidedEvent();
|
||||||
|
|
||||||
// @if TARGET='web'
|
// @if TARGET='web'
|
||||||
Lbryio.call('user_tag', 'edit', { add: 'lbrytv' });
|
Lbryio.call('user_tag', 'edit', { add: 'lbrytv' });
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doHideModal } from 'redux/actions/app';
|
import { doHideModal } from 'redux/actions/app';
|
||||||
import { doToast, doUploadThumbnail } from 'lbry-redux';
|
import { doToast, doUploadThumbnail } from 'lbry-redux';
|
||||||
|
import fs from 'fs';
|
||||||
import ModalAutoGenerateThumbnail from './view';
|
import ModalAutoGenerateThumbnail from './view';
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
closeModal: () => dispatch(doHideModal()),
|
closeModal: () => dispatch(doHideModal()),
|
||||||
upload: buffer => dispatch(doUploadThumbnail(null, buffer)),
|
upload: buffer => dispatch(doUploadThumbnail(null, buffer, null, fs)),
|
||||||
showToast: options => dispatch(doToast(options)),
|
showToast: options => dispatch(doToast(options)),
|
||||||
should these 3rd and 4th params be re-ordered? (meh) should these 3rd and 4th params be re-ordered? (meh)
The third param is the android equivalent of The third param is the android equivalent of `fs`
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doHideModal } from 'redux/actions/app';
|
import { doHideModal } from 'redux/actions/app';
|
||||||
import { doUploadThumbnail, doUpdatePublishForm } from 'lbry-redux';
|
import { doUploadThumbnail, doUpdatePublishForm } from 'lbry-redux';
|
||||||
|
import fs from 'fs';
|
||||||
import ModalConfirmThumbnailUpload from './view';
|
import ModalConfirmThumbnailUpload from './view';
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
closeModal: () => dispatch(doHideModal()),
|
closeModal: () => dispatch(doHideModal()),
|
||||||
upload: path => dispatch(doUploadThumbnail(path)),
|
upload: path => dispatch(doUploadThumbnail(path, null, null, fs)),
|
||||||
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
||||||
same question as above, if when does JS get named parameters?! same question as above, if `fs` is regularly provided but other params are not, consider moving fs up in param order
when does JS get named parameters?!
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,19 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doDeleteFileAndMaybeGoBack } from 'redux/actions/file';
|
import { doDeleteFileAndMaybeGoBack } from 'redux/actions/file';
|
||||||
import {
|
import { makeSelectTitleForUri, makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
|
||||||
makeSelectTitleForUri,
|
|
||||||
makeSelectClaimIsMine,
|
|
||||||
makeSelectFileInfoForUri,
|
|
||||||
makeSelectClaimForUri,
|
|
||||||
} from 'lbry-redux';
|
|
||||||
import { doHideModal } from 'redux/actions/app';
|
import { doHideModal } from 'redux/actions/app';
|
||||||
import ModalRemoveFile from './view';
|
import ModalRemoveFile from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
title: makeSelectTitleForUri(props.uri)(state),
|
title: makeSelectTitleForUri(props.uri)(state),
|
||||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
closeModal: () => dispatch(doHideModal()),
|
closeModal: () => dispatch(doHideModal()),
|
||||||
deleteFile: (fileInfo, deleteFromComputer, abandonClaim) => {
|
deleteFile: (uri, deleteFromComputer, abandonClaim) => {
|
||||||
dispatch(doDeleteFileAndMaybeGoBack(fileInfo, deleteFromComputer, abandonClaim));
|
dispatch(doDeleteFileAndMaybeGoBack(uri, deleteFromComputer, abandonClaim));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Button from 'component/button';
|
||||||
import usePersistedState from 'util/use-persisted-state';
|
import usePersistedState from 'util/use-persisted-state';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
uri: string,
|
||||||
claim: StreamClaim,
|
claim: StreamClaim,
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
closeModal: () => void,
|
closeModal: () => void,
|
||||||
|
@ -17,11 +18,9 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function ModalRemoveFile(props: Props) {
|
function ModalRemoveFile(props: Props) {
|
||||||
const { claim, claimIsMine, closeModal, deleteFile, fileInfo, title } = props;
|
const { uri, claimIsMine, closeModal, deleteFile, title } = props;
|
||||||
const [deleteChecked, setDeleteChecked] = usePersistedState('modal-remove-file:delete', true);
|
const [deleteChecked, setDeleteChecked] = usePersistedState('modal-remove-file:delete', true);
|
||||||
const [abandonChecked, setAbandonChecked] = usePersistedState('modal-remove-file:abandon', true);
|
const [abandonChecked, setAbandonChecked] = usePersistedState('modal-remove-file:abandon', true);
|
||||||
const { txid, nout } = claim;
|
|
||||||
const outpoint = fileInfo ? fileInfo.outpoint : `${txid}:${nout}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen title="Remove File" contentLabel={__('Confirm File Remove')} type="custom" onAborted={closeModal}>
|
<Modal isOpen title="Remove File" contentLabel={__('Confirm File Remove')} type="custom" onAborted={closeModal}>
|
||||||
|
@ -30,7 +29,7 @@ function ModalRemoveFile(props: Props) {
|
||||||
{__("Are you sure you'd like to remove")} <cite>{`"${title}"`}</cite> {__('from the LBRY app?')}
|
{__("Are you sure you'd like to remove")} <cite>{`"${title}"`}</cite> {__('from the LBRY app?')}
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<Form onSubmit={() => deleteFile(outpoint || '', deleteChecked, abandonChecked)}>
|
<Form onSubmit={() => deleteFile(uri, deleteChecked, claimIsMine ? abandonChecked : false)}>
|
||||||
<FormField
|
<FormField
|
||||||
name="file_delete"
|
name="file_delete"
|
||||||
label={__('Also delete this file from my computer')}
|
label={__('Also delete this file from my computer')}
|
||||||
|
@ -49,13 +48,7 @@ function ModalRemoveFile(props: Props) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="card__actions">
|
<div className="card__actions">
|
||||||
<Button
|
<Button type="submit" button="primary" label={__('OK')} disabled={!deleteChecked && !abandonChecked} />
|
||||||
autoFocus
|
|
||||||
button="primary"
|
|
||||||
label={__('OK')}
|
|
||||||
disabled={!deleteChecked && !abandonChecked}
|
|
||||||
onClick={() => deleteFile(outpoint || '', deleteChecked, abandonChecked)}
|
|
||||||
/>
|
|
||||||
<Button button="link" label={__('Cancel')} onClick={closeModal} />
|
<Button button="link" label={__('Cancel')} onClick={closeModal} />
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { doFetchViewCount, makeSelectViewCountForUri, makeSelectCostInfoForUri,
|
||||||
import { selectShowMatureContent, makeSelectClientSetting } from 'redux/selectors/settings';
|
import { selectShowMatureContent, makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
|
import fs from 'fs';
|
||||||
import FilePage from './view';
|
import FilePage from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
|
@ -47,7 +48,7 @@ const perform = dispatch => ({
|
||||||
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
||||||
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
||||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||||
prepareEdit: (publishData, uri, fileInfo) => dispatch(doPrepareEdit(publishData, uri, fileInfo)),
|
prepareEdit: (publishData, uri, fileInfo) => dispatch(doPrepareEdit(publishData, uri, fileInfo, fs)),
|
||||||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||||
setViewed: uri => dispatch(doSetContentHistoryItem(uri)),
|
setViewed: uri => dispatch(doSetContentHistoryItem(uri)),
|
||||||
markSubscriptionRead: (channel, uri) => dispatch(doRemoveUnreadSubscription(channel, uri)),
|
markSubscriptionRead: (channel, uri) => dispatch(doRemoveUnreadSubscription(channel, uri)),
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { connect } from 'react-redux';
|
||||||
import * as settings from 'constants/settings';
|
import * as settings from 'constants/settings';
|
||||||
import { doClearCache, doNotifyEncryptWallet, doNotifyDecryptWallet } from 'redux/actions/app';
|
import { doClearCache, doNotifyEncryptWallet, doNotifyDecryptWallet } from 'redux/actions/app';
|
||||||
import { doSetDaemonSetting, doSetClientSetting, doGetThemes, doChangeLanguage } from 'redux/actions/settings';
|
import { doSetDaemonSetting, doSetClientSetting, doGetThemes, doChangeLanguage } from 'redux/actions/settings';
|
||||||
|
import { doSetPlayingUri } from 'redux/actions/content';
|
||||||
import {
|
import {
|
||||||
makeSelectClientSetting,
|
makeSelectClientSetting,
|
||||||
selectDaemonSettings,
|
selectDaemonSettings,
|
||||||
|
@ -40,6 +41,7 @@ const perform = dispatch => ({
|
||||||
encryptWallet: () => dispatch(doNotifyEncryptWallet()),
|
encryptWallet: () => dispatch(doNotifyEncryptWallet()),
|
||||||
decryptWallet: () => dispatch(doNotifyDecryptWallet()),
|
decryptWallet: () => dispatch(doNotifyDecryptWallet()),
|
||||||
updateWalletStatus: () => dispatch(doWalletStatus()),
|
updateWalletStatus: () => dispatch(doWalletStatus()),
|
||||||
|
clearPlayingUri: () => dispatch(doSetPlayingUri(null)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|
|
@ -168,6 +168,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
hideBalance,
|
hideBalance,
|
||||||
userBlockedChannelsCount,
|
userBlockedChannelsCount,
|
||||||
floatingPlayer,
|
floatingPlayer,
|
||||||
|
clearPlayingUri,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
|
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
|
||||||
|
@ -319,6 +320,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
name="floating_player"
|
name="floating_player"
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
setClientSetting(SETTINGS.FLOATING_PLAYER, !floatingPlayer);
|
setClientSetting(SETTINGS.FLOATING_PLAYER, !floatingPlayer);
|
||||||
|
clearPlayingUri();
|
||||||
}}
|
}}
|
||||||
checked={floatingPlayer}
|
checked={floatingPlayer}
|
||||||
label={__('Floating video player')}
|
label={__('Floating video player')}
|
||||||
|
|
|
@ -6,7 +6,14 @@ import { ipcRenderer, remote } from 'electron';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
import * as MODALS from 'constants/modal_types';
|
import * as MODALS from 'constants/modal_types';
|
||||||
import { Lbry, doBalanceSubscribe, doFetchFileInfosAndPublishedClaims, doError } from 'lbry-redux';
|
import {
|
||||||
|
Lbry,
|
||||||
|
doBalanceSubscribe,
|
||||||
|
doFetchFileInfosAndPublishedClaims,
|
||||||
|
doError,
|
||||||
|
makeSelectClaimForUri,
|
||||||
|
makeSelectClaimIsMine,
|
||||||
|
} from 'lbry-redux';
|
||||||
import Native from 'native';
|
import Native from 'native';
|
||||||
import { doFetchDaemonSettings } from 'redux/actions/settings';
|
import { doFetchDaemonSettings } from 'redux/actions/settings';
|
||||||
import { doCheckSubscriptionsInit } from 'redux/actions/subscriptions';
|
import { doCheckSubscriptionsInit } from 'redux/actions/subscriptions';
|
||||||
|
@ -24,6 +31,7 @@ import {
|
||||||
import { doAuthenticate } from 'lbryinc';
|
import { doAuthenticate } from 'lbryinc';
|
||||||
import { lbrySettings as config, version as appVersion } from 'package.json';
|
import { lbrySettings as config, version as appVersion } from 'package.json';
|
||||||
import { push } from 'connected-react-router';
|
import { push } from 'connected-react-router';
|
||||||
|
import analytics from 'analytics';
|
||||||
|
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
const { autoUpdater } = remote.require('electron-updater');
|
const { autoUpdater } = remote.require('electron-updater');
|
||||||
|
@ -410,3 +418,18 @@ export function doToggleSearchExpanded() {
|
||||||
type: ACTIONS.TOGGLE_SEARCH_EXPANDED,
|
type: ACTIONS.TOGGLE_SEARCH_EXPANDED,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doAnalyticsView(uri, timeToStart) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const { txid, nout, claim_id: claimId } = makeSelectClaimForUri(uri)(state);
|
||||||
|
const claimIsMine = makeSelectClaimIsMine(uri)(state);
|
||||||
|
const outpoint = `${txid}:${nout}`;
|
||||||
|
|
||||||
|
if (claimIsMine) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
analytics.apiLogView(uri, outpoint, claimId, timeToStart);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -225,6 +225,11 @@ export function doPlayUri(uri: string, skipCostCheck: boolean = false, saveFileO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fileInfo && saveFile && (!fileInfo.download_path || !fileInfo.written_bytes)) {
|
||||||
|
beginGetFile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (cost === 0 || skipCostCheck) {
|
if (cost === 0 || skipCostCheck) {
|
||||||
beginGetFile();
|
beginGetFile();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -2,9 +2,11 @@ import * as ACTIONS from 'constants/action_types';
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
import { shell } from 'electron';
|
import { shell } from 'electron';
|
||||||
// @endif
|
// @endif
|
||||||
import { Lbry, batchActions, doAbandonClaim, selectMyClaimsOutpoints } from 'lbry-redux';
|
import { Lbry, batchActions, doAbandonClaim, selectMyClaimsOutpoints, makeSelectFileInfoForUri } from 'lbry-redux';
|
||||||
import { doHideModal } from 'redux/actions/app';
|
import { doHideModal } from 'redux/actions/app';
|
||||||
import { goBack } from 'connected-react-router';
|
import { goBack } from 'connected-react-router';
|
||||||
|
import { doSetPlayingUri } from 'redux/actions/content';
|
||||||
|
import { selectPlayingUri } from 'redux/selectors/content';
|
||||||
|
|
||||||
export function doOpenFileInFolder(path) {
|
export function doOpenFileInFolder(path) {
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -47,14 +49,23 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doDeleteFileAndMaybeGoBack(fileInfo, deleteFromComputer, abandonClaim) {
|
export function doDeleteFileAndMaybeGoBack(uri, deleteFromComputer, abandonClaim) {
|
||||||
return dispatch => {
|
return (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const playingUri = selectPlayingUri(state);
|
||||||
|
const { outpoint } = makeSelectFileInfoForUri(uri)(state);
|
||||||
const actions = [];
|
const actions = [];
|
||||||
actions.push(doHideModal());
|
actions.push(doHideModal());
|
||||||
actions.push(doDeleteFile(fileInfo, deleteFromComputer, abandonClaim));
|
actions.push(doDeleteFile(outpoint, deleteFromComputer, abandonClaim));
|
||||||
dispatch(batchActions(...actions));
|
|
||||||
if (abandonClaim) {
|
if (playingUri === uri) {
|
||||||
dispatch(goBack());
|
actions.push(doSetPlayingUri(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (abandonClaim) {
|
||||||
|
actions.push(goBack());
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(batchActions(...actions));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -266,7 +266,11 @@ export const makeSelectIsSubscribed = uri =>
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we couldn't get a channel uri from the claim uri, the uri passed in might be a channel already
|
// If we couldn't get a channel uri from the claim uri, the uri passed in might be a channel already
|
||||||
const { isChannel } = parseURI(uri);
|
let isChannel;
|
||||||
|
try {
|
||||||
|
({ isChannel } = parseURI(uri));
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
if (isChannel) {
|
if (isChannel) {
|
||||||
const uriWithPrefix = uri.startsWith('lbry://') ? uri : `lbry://${uri}`;
|
const uriWithPrefix = uri.startsWith('lbry://') ? uri : `lbry://${uri}`;
|
||||||
return subscriptions.some(sub => sub.uri === uriWithPrefix);
|
return subscriptions.some(sub => sub.uri === uriWithPrefix);
|
||||||
|
|
|
@ -94,6 +94,16 @@
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Quick fix because this is a pain
|
||||||
|
// There is something weird with wrapping buttons. Some places we want to wrap and others we want to ellips
|
||||||
Good job noting why the rules are being broken so a future dev and/or future you can understand Good job noting why the rules are being broken so a future dev and/or future you can understand
|
|||||||
|
// Probably requires some nested style cleanup
|
||||||
|
.button--download-link {
|
||||||
|
.button__label {
|
||||||
|
white-space: normal;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.button__content {
|
.button__content {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -85,11 +85,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.content__loading {
|
.content__loading {
|
||||||
position: absolute;
|
height: 100%;
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -95,4 +95,11 @@
|
||||||
.vjs-big-play-button {
|
.vjs-big-play-button {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vjs-modal-dialog .vjs-modal-dialog-content {
|
||||||
|
position: relative;
|
||||||
|
padding-top: 5rem;
|
||||||
|
// Make sure no videojs message interferes with overlaying buttons
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,6 +98,11 @@ hr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
a {
|
||||||
Confident in no consequences to this rule being so universal? Confident in no consequences to this rule being so universal?
97% confidence 97% confidence
|
|||||||
|
-webkit-user-drag: none;
|
||||||
|
}
|
||||||
|
|
||||||
.columns {
|
.columns {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
|
@ -12,7 +12,7 @@ export default function usePersistedState(key, firstTimeDefault) {
|
||||||
parsedItem = JSON.parse(item);
|
parsedItem = JSON.parse(item);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
if (parsedItem) {
|
if (parsedItem !== undefined) {
|
||||||
defaultValue = parsedItem;
|
defaultValue = parsedItem;
|
||||||
} else {
|
} else {
|
||||||
defaultValue = item;
|
defaultValue = item;
|
||||||
|
|
11
src/ui/util/use-previous.js
Normal file
11
src/ui/util/use-previous.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
export default function usePrevious(value) {
|
||||||
|
const ref = useRef();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ref.current = value;
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return ref.current;
|
||||||
|
}
|
|
@ -6762,18 +6762,18 @@ lazy-val@^1.0.3, lazy-val@^1.0.4:
|
||||||
yargs "^13.2.2"
|
yargs "^13.2.2"
|
||||||
zstd-codec "^0.1.1"
|
zstd-codec "^0.1.1"
|
||||||
|
|
||||||
lbry-redux@lbryio/lbry-redux#05e70648e05c51c51710f6dd698a8e2219b54df2:
|
lbry-redux@lbryio/lbry-redux#6005fa245a888e2de045d7e42411847de7943f52:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/05e70648e05c51c51710f6dd698a8e2219b54df2"
|
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/6005fa245a888e2de045d7e42411847de7943f52"
|
||||||
dependencies:
|
dependencies:
|
||||||
mime "^2.4.4"
|
mime "^2.4.4"
|
||||||
proxy-polyfill "0.1.6"
|
proxy-polyfill "0.1.6"
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
uuid "^3.3.2"
|
uuid "^3.3.2"
|
||||||
|
|
||||||
lbryinc@lbryio/lbryinc#a93596c51c8fb0a226cb84df04c26a6bb60a45fb:
|
lbryinc@lbryio/lbryinc#1ce266b3c52654190b955e9c869b8e302aa5c585:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/a93596c51c8fb0a226cb84df04c26a6bb60a45fb"
|
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/1ce266b3c52654190b955e9c869b8e302aa5c585"
|
||||||
dependencies:
|
dependencies:
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue
Making a
sendReactGAEvent
or equivalent function would eliminate these repeated if checks and could also help ensure the checks are not accidentally left out in the future.