Random fixes #2729
32 changed files with 249 additions and 101 deletions
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"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.",
|
||||
"keywords": [
|
||||
"lbry"
|
||||
|
@ -125,8 +125,8 @@
|
|||
"jsmediatags": "^3.8.1",
|
||||
"json-loader": "^0.5.4",
|
||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||
"lbry-redux": "lbryio/lbry-redux#05e70648e05c51c51710f6dd698a8e2219b54df2",
|
||||
"lbryinc": "lbryio/lbryinc#a93596c51c8fb0a226cb84df04c26a6bb60a45fb",
|
||||
"lbry-redux": "lbryio/lbry-redux#6005fa245a888e2de045d7e42411847de7943f52",
|
||||
"lbryinc": "lbryio/lbryinc#1ce266b3c52654190b955e9c869b8e302aa5c585",
|
||||
"lint-staged": "^7.0.2",
|
||||
"localforage": "^1.7.1",
|
||||
"lodash-es": "^4.17.14",
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Lbryio } from 'lbryinc';
|
|||
import ReactGA from 'react-ga';
|
||||
import { history } from './store';
|
||||
// @if TARGET='app'
|
||||
import Native from 'native';
|
||||
import ElectronCookies from '@exponent/electron-cookies';
|
||||
// @endif
|
||||
|
||||
|
@ -15,6 +16,9 @@ type Analytics = {
|
|||
apiLogView: (string, string, string, ?number, ?() => void) => void,
|
||||
apiLogPublish: () => void,
|
||||
tagFollowEvent: (string, boolean, string) => void,
|
||||
emailProvidedEvent: () => void,
|
||||
emailVerifiedEvent: () => void,
|
||||
rewardEligibleEvent: () => void,
|
||||
};
|
||||
|
||||
let analyticsEnabled: boolean = true;
|
||||
|
@ -26,11 +30,15 @@ const analytics: Analytics = {
|
|||
},
|
||||
setUser: userId => {
|
||||
if (analyticsEnabled && userId) {
|
||||
ReactGA.event({
|
||||
category: 'User',
|
||||
action: 'userId',
|
||||
value: userId,
|
||||
ReactGA.set({
|
||||
userId,
|
||||
});
|
||||
|
||||
// @if TARGET='app'
|
||||
Native.getAppVersionInfo().then(({ localVersion }) => {
|
||||
sendGaEvent('Desktop-Version', localVersion);
|
||||
});
|
||||
// @endif
|
||||
}
|
||||
},
|
||||
toggle: (enabled: boolean): void => {
|
||||
|
@ -39,8 +47,8 @@ const analytics: Analytics = {
|
|||
analyticsEnabled = enabled;
|
||||
// @endif
|
||||
},
|
||||
apiLogView: (uri, outpoint, claimId, timeToStart, onSuccessCb) => {
|
||||
if (analyticsEnabled) {
|
||||
apiLogView: (uri, outpoint, claimId, timeToStart) => {
|
||||
if (analyticsEnabled && isProduction) {
|
||||
const params: {
|
||||
uri: string,
|
||||
outpoint: string,
|
||||
|
@ -52,17 +60,12 @@ const analytics: Analytics = {
|
|||
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;
|
||||
}
|
||||
|
||||
Lbryio.call('file', 'view', params)
|
||||
.then(() => {
|
||||
if (onSuccessCb) {
|
||||
onSuccessCb();
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
Lbryio.call('file', 'view', params);
|
||||
}
|
||||
},
|
||||
apiLogSearch: () => {
|
||||
|
@ -82,23 +85,31 @@ const analytics: Analytics = {
|
|||
}
|
||||
},
|
||||
tagFollowEvent: (tag, following, location) => {
|
||||
if (analyticsEnabled) {
|
||||
ReactGA.event({
|
||||
category: following ? 'Tag-Follow' : 'Tag-Unfollow',
|
||||
action: tag,
|
||||
});
|
||||
}
|
||||
sendGaEvent(following ? 'Tag-Follow' : 'Tag-Unfollow', tag);
|
||||
},
|
||||
channelBlockEvent: (uri, blocked, location) => {
|
||||
if (analyticsEnabled) {
|
||||
ReactGA.event({
|
||||
category: blocked ? 'Channel-Hidden' : 'Channel-Unhidden',
|
||||
action: uri,
|
||||
});
|
||||
}
|
||||
sendGaEvent(blocked ? 'Channel-Hidden' : 'Channel-Unhidden', uri);
|
||||
},
|
||||
emailProvidedEvent: () => {
|
||||
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
|
||||
// Set `debug: true` for debug info
|
||||
// Will change once we have separate ids for desktop/web
|
||||
|
@ -113,6 +124,8 @@ ElectronCookies.enable({
|
|||
ReactGA.initialize(UA_ID, {
|
||||
testMode: process.env.NODE_ENV !== 'production',
|
||||
cookieDomain: 'auto',
|
||||
// un-comment to see events as they are sent to google
|
||||
// debug: true,
|
||||
});
|
||||
|
||||
// Manually call the first page view
|
||||
|
|
|
@ -12,6 +12,7 @@ import useKonamiListener from 'util/enhanced-layout';
|
|||
import Yrbl from 'component/yrbl';
|
||||
import FileViewer from 'component/fileViewer';
|
||||
import { withRouter } from 'react-router';
|
||||
import usePrevious from 'util/use-previous';
|
||||
|
||||
export const MAIN_WRAPPER_CLASS = 'main-wrapper';
|
||||
|
||||
|
@ -21,7 +22,7 @@ type Props = {
|
|||
language: string,
|
||||
theme: string,
|
||||
accessToken: ?string,
|
||||
user: ?{ id: string, has_verified_email: boolean },
|
||||
user: ?{ id: string, has_verified_email: boolean, is_reward_approved: boolean },
|
||||
location: { pathname: string },
|
||||
fetchRewards: () => void,
|
||||
fetchRewardedContent: () => void,
|
||||
|
@ -35,7 +36,10 @@ function App(props: Props) {
|
|||
const isEnhancedLayout = useKonamiListener();
|
||||
const userId = user && user.id;
|
||||
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 urlParts = pathname.split('/');
|
||||
const claimName = urlParts[1];
|
||||
|
@ -65,10 +69,24 @@ function App(props: Props) {
|
|||
}, [theme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (userId) {
|
||||
if (previousUserId === undefined && 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'
|
||||
useEffect(() => {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import * as PAGES from 'constants/pages';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
doResolveUri,
|
||||
|
@ -10,11 +11,15 @@ import {
|
|||
makeSelectClaimIsNsfw,
|
||||
selectBlockedChannels,
|
||||
selectChannelIsBlocked,
|
||||
doClearPublish,
|
||||
doPrepareEdit,
|
||||
} from 'lbry-redux';
|
||||
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||
import { makeSelectHasVisitedUri } from 'redux/selectors/content';
|
||||
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||
import { push } from 'connected-react-router';
|
||||
|
||||
import ClaimPreview from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
@ -36,6 +41,11 @@ const select = (state, props) => ({
|
|||
|
||||
const perform = dispatch => ({
|
||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||
beginPublish: name => {
|
||||
dispatch(doClearPublish());
|
||||
dispatch(doPrepareEdit({ name }));
|
||||
dispatch(push(`/$/${PAGES.PUBLISH}`));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -44,6 +44,7 @@ type Props = {
|
|||
blockedChannelUris: Array<string>,
|
||||
channelIsBlocked: boolean,
|
||||
isSubscribed: boolean,
|
||||
beginPublish: string => void,
|
||||
};
|
||||
|
||||
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||
|
@ -68,6 +69,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
showUserBlocked,
|
||||
channelIsBlocked,
|
||||
isSubscribed,
|
||||
beginPublish,
|
||||
} = props;
|
||||
const haventFetched = claim === undefined;
|
||||
const abandoned = !isResolvingUri && !claim;
|
||||
|
@ -77,8 +79,9 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
const hideActions = type === 'small' || type === 'tooltip';
|
||||
|
||||
let isValid;
|
||||
let name;
|
||||
try {
|
||||
parseURI(uri);
|
||||
({ claimName: name } = parseURI(uri));
|
||||
isValid = true;
|
||||
} catch (e) {
|
||||
isValid = false;
|
||||
|
@ -191,7 +194,11 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
<Fragment>
|
||||
<div>{__('Publish something and claim this spot!')}</div>
|
||||
<div className="card__actions">
|
||||
<Button button="primary" label={`${__('Publish to')} ${uri}`} />
|
||||
<Button
|
||||
onClick={() => beginPublish(name)}
|
||||
button="primary"
|
||||
label={`${__('Publish to')} ${uri}`}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
|
|
|
@ -25,7 +25,7 @@ class IconComponent extends React.PureComponent<Props> {
|
|||
case ICONS.FEATURED:
|
||||
return __('Featured content. Earn rewards for watching.');
|
||||
case ICONS.DOWNLOAD:
|
||||
return __('This file is downloaded.');
|
||||
return __('This file is in your library.');
|
||||
case ICONS.SUBSCRIBE:
|
||||
return __('You are subscribed to this channel.');
|
||||
case ICONS.SETTINGS:
|
||||
|
|
|
@ -16,9 +16,7 @@ type Props = {
|
|||
class FileActions extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const { fileInfo, uri, openModal, claimIsMine, claimId } = this.props;
|
||||
const showDelete =
|
||||
claimIsMine ||
|
||||
(fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed === fileInfo.blobs_in_stream));
|
||||
const showDelete = claimIsMine || (fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed > 0));
|
||||
return (
|
||||
<React.Fragment>
|
||||
{showDelete && (
|
||||
|
|
|
@ -77,6 +77,7 @@ class FileDetails extends PureComponent<Props> {
|
|||
{': '}
|
||||
<Button
|
||||
button="link"
|
||||
className="button--download-link"
|
||||
onClick={() => {
|
||||
if (downloadPath) {
|
||||
openFolder(downloadPath);
|
||||
|
|
|
@ -20,12 +20,10 @@ type Props = {
|
|||
function FileDownloadLink(props: 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 label =
|
||||
fileInfo && fileInfo.written_bytes > 0
|
||||
? __('Downloading: ') + progress.toFixed(0) + __('% complete')
|
||||
: __('Connecting...');
|
||||
fileInfo && fileInfo.written_bytes > 0 ? progress.toFixed(0) + __('% downloaded') : __('Connecting...');
|
||||
|
||||
return <span>{label}</span>;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectFileInfoForUri, makeSelectClaimIsMine } from 'lbry-redux';
|
||||
import { makeSelectFilePartlyDownloaded, makeSelectClaimIsMine } from 'lbry-redux';
|
||||
import { selectRewardContentClaimIds } from 'lbryinc';
|
||||
import { makeSelectIsSubscribed, makeSelectIsNew } from 'redux/selectors/subscriptions';
|
||||
import FileProperties from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
|
||||
downloaded: !!makeSelectFileInfoForUri(props.uri)(state),
|
||||
downloaded: makeSelectFilePartlyDownloaded(props.uri)(state),
|
||||
isSubscribed: makeSelectIsSubscribed(props.uri)(state),
|
||||
isNew: makeSelectIsNew(props.uri)(state),
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
|
|
|
@ -12,6 +12,7 @@ import { makeSelectIsPlaying, makeSelectShouldObscurePreview, selectPlayingUri }
|
|||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { doSetPlayingUri } from 'redux/actions/content';
|
||||
import { withRouter } from 'react-router';
|
||||
import { doAnalyticsView } from 'redux/actions/app';
|
||||
import FileViewer from './view';
|
||||
|
||||
const select = (state, props) => {
|
||||
|
@ -32,6 +33,7 @@ const select = (state, props) => {
|
|||
|
||||
const perform = dispatch => ({
|
||||
clearPlayingUri: () => dispatch(doSetPlayingUri(null)),
|
||||
triggerAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
|
||||
});
|
||||
|
||||
export default withRouter(
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
// @flow
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Button from 'component/button';
|
||||
import classnames from 'classnames';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
import FileRender from 'component/fileRender';
|
||||
import UriIndicator from 'component/uriIndicator';
|
||||
import usePersistedState from 'util/use-persisted-state';
|
||||
import usePrevious from 'util/use-previous';
|
||||
import { FILE_WRAPPER_CLASS } from 'page/file/view';
|
||||
import Draggable from 'react-draggable';
|
||||
import Tooltip from 'component/common/tooltip';
|
||||
|
@ -27,6 +28,7 @@ type Props = {
|
|||
title: ?string,
|
||||
floatingPlayerEnabled: boolean,
|
||||
clearPlayingUri: () => void,
|
||||
triggerAnalyticsView: (string, number) => void,
|
||||
};
|
||||
|
||||
export default function FileViewer(props: Props) {
|
||||
|
@ -40,7 +42,9 @@ export default function FileViewer(props: Props) {
|
|||
title,
|
||||
clearPlayingUri,
|
||||
floatingPlayerEnabled,
|
||||
triggerAnalyticsView,
|
||||
} = props;
|
||||
const [playTime, setPlayTime] = useState();
|
||||
const [fileViewerRect, setFileViewerRect] = usePersistedState('inline-file-viewer:rect');
|
||||
const [position, setPosition] = usePersistedState('floating-file-viewer:position', {
|
||||
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.")
|
||||
: __('Loading');
|
||||
|
||||
function handleDrag(e, ui) {
|
||||
const { x, y } = position;
|
||||
const newX = x + ui.deltaX;
|
||||
const newY = y + ui.deltaY;
|
||||
setPosition({
|
||||
x: newX,
|
||||
y: newY,
|
||||
});
|
||||
}
|
||||
const previousUri = usePrevious(uri);
|
||||
const previousIsReadyToPlay = usePrevious(isReadyToPlay);
|
||||
const isNewView = uri && previousUri !== uri && isPlaying;
|
||||
const wasntReadyButNowItIs = isReadyToPlay && !previousIsReadyToPlay;
|
||||
|
||||
useEffect(() => {
|
||||
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(() => {
|
||||
function handleResize() {
|
||||
|
@ -85,9 +98,18 @@ export default function FileViewer(props: Props) {
|
|||
}
|
||||
}, [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));
|
||||
@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) {
|
||||
clearPlayingUri();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,18 @@ type 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 fileStatus = fileInfo && fileInfo.status;
|
||||
|
@ -64,10 +75,10 @@ export default function FileViewer(props: Props) {
|
|||
|
||||
useEffect(() => {
|
||||
const videoOnPage = document.querySelector('video');
|
||||
if (autoplay && !videoOnPage) {
|
||||
if (autoplay && !videoOnPage && isStreamable) {
|
||||
viewFile();
|
||||
}
|
||||
}, [autoplay, viewFile]);
|
||||
}, [autoplay, viewFile, isStreamable]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -3,6 +3,7 @@ import * as React from 'react';
|
|||
import { FormField, Form } from 'component/common/form';
|
||||
import Button from 'component/button';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import analytics from 'analytics';
|
||||
|
||||
type Props = {
|
||||
cancelButton: React.Node,
|
||||
|
@ -37,6 +38,7 @@ class UserEmailNew extends React.PureComponent<Props, State> {
|
|||
const { email } = this.state;
|
||||
const { addUserEmail } = this.props;
|
||||
addUserEmail(email);
|
||||
analytics.emailProvidedEvent();
|
||||
|
||||
// @if TARGET='web'
|
||||
Lbryio.call('user_tag', 'edit', { add: 'lbrytv' });
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doHideModal } from 'redux/actions/app';
|
||||
import { doToast, doUploadThumbnail } from 'lbry-redux';
|
||||
import fs from 'fs';
|
||||
import ModalAutoGenerateThumbnail from './view';
|
||||
|
||||
const perform = dispatch => ({
|
||||
closeModal: () => dispatch(doHideModal()),
|
||||
upload: buffer => dispatch(doUploadThumbnail(null, buffer)),
|
||||
upload: buffer => dispatch(doUploadThumbnail(null, buffer, null, fs)),
|
||||
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 { doHideModal } from 'redux/actions/app';
|
||||
import { doUploadThumbnail, doUpdatePublishForm } from 'lbry-redux';
|
||||
import fs from 'fs';
|
||||
import ModalConfirmThumbnailUpload from './view';
|
||||
|
||||
const perform = dispatch => ({
|
||||
closeModal: () => dispatch(doHideModal()),
|
||||
upload: path => dispatch(doUploadThumbnail(path)),
|
||||
upload: path => dispatch(doUploadThumbnail(path, null, null, fs)),
|
||||
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 { doDeleteFileAndMaybeGoBack } from 'redux/actions/file';
|
||||
import {
|
||||
makeSelectTitleForUri,
|
||||
makeSelectClaimIsMine,
|
||||
makeSelectFileInfoForUri,
|
||||
makeSelectClaimForUri,
|
||||
} from 'lbry-redux';
|
||||
import { makeSelectTitleForUri, makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { doHideModal } from 'redux/actions/app';
|
||||
import ModalRemoveFile from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
title: makeSelectTitleForUri(props.uri)(state),
|
||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
closeModal: () => dispatch(doHideModal()),
|
||||
deleteFile: (fileInfo, deleteFromComputer, abandonClaim) => {
|
||||
dispatch(doDeleteFileAndMaybeGoBack(fileInfo, deleteFromComputer, abandonClaim));
|
||||
deleteFile: (uri, deleteFromComputer, abandonClaim) => {
|
||||
dispatch(doDeleteFileAndMaybeGoBack(uri, deleteFromComputer, abandonClaim));
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import Button from 'component/button';
|
|||
import usePersistedState from 'util/use-persisted-state';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
claim: StreamClaim,
|
||||
claimIsMine: boolean,
|
||||
closeModal: () => void,
|
||||
|
@ -17,11 +18,9 @@ type 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 [abandonChecked, setAbandonChecked] = usePersistedState('modal-remove-file:abandon', true);
|
||||
const { txid, nout } = claim;
|
||||
const outpoint = fileInfo ? fileInfo.outpoint : `${txid}:${nout}`;
|
||||
|
||||
return (
|
||||
<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?')}
|
||||
</p>
|
||||
</section>
|
||||
<Form onSubmit={() => deleteFile(outpoint || '', deleteChecked, abandonChecked)}>
|
||||
<Form onSubmit={() => deleteFile(uri, deleteChecked, claimIsMine ? abandonChecked : false)}>
|
||||
<FormField
|
||||
name="file_delete"
|
||||
label={__('Also delete this file from my computer')}
|
||||
|
@ -49,13 +48,7 @@ function ModalRemoveFile(props: Props) {
|
|||
/>
|
||||
)}
|
||||
<div className="card__actions">
|
||||
<Button
|
||||
autoFocus
|
||||
button="primary"
|
||||
label={__('OK')}
|
||||
disabled={!deleteChecked && !abandonChecked}
|
||||
onClick={() => deleteFile(outpoint || '', deleteChecked, abandonChecked)}
|
||||
/>
|
||||
<Button type="submit" button="primary" label={__('OK')} disabled={!deleteChecked && !abandonChecked} />
|
||||
<Button button="link" label={__('Cancel')} onClick={closeModal} />
|
||||
</div>
|
||||
</Form>
|
||||
|
|
|
@ -22,6 +22,7 @@ import { doFetchViewCount, makeSelectViewCountForUri, makeSelectCostInfoForUri,
|
|||
import { selectShowMatureContent, makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import fs from 'fs';
|
||||
import FilePage from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
@ -47,7 +48,7 @@ const perform = dispatch => ({
|
|||
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
||||
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
||||
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)),
|
||||
setViewed: uri => dispatch(doSetContentHistoryItem(uri)),
|
||||
markSubscriptionRead: (channel, uri) => dispatch(doRemoveUnreadSubscription(channel, uri)),
|
||||
|
|
|
@ -2,6 +2,7 @@ import { connect } from 'react-redux';
|
|||
import * as settings from 'constants/settings';
|
||||
import { doClearCache, doNotifyEncryptWallet, doNotifyDecryptWallet } from 'redux/actions/app';
|
||||
import { doSetDaemonSetting, doSetClientSetting, doGetThemes, doChangeLanguage } from 'redux/actions/settings';
|
||||
import { doSetPlayingUri } from 'redux/actions/content';
|
||||
import {
|
||||
makeSelectClientSetting,
|
||||
selectDaemonSettings,
|
||||
|
@ -40,6 +41,7 @@ const perform = dispatch => ({
|
|||
encryptWallet: () => dispatch(doNotifyEncryptWallet()),
|
||||
decryptWallet: () => dispatch(doNotifyDecryptWallet()),
|
||||
updateWalletStatus: () => dispatch(doWalletStatus()),
|
||||
clearPlayingUri: () => dispatch(doSetPlayingUri(null)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -168,6 +168,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
hideBalance,
|
||||
userBlockedChannelsCount,
|
||||
floatingPlayer,
|
||||
clearPlayingUri,
|
||||
} = this.props;
|
||||
|
||||
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
|
||||
|
@ -319,6 +320,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
name="floating_player"
|
||||
onChange={() => {
|
||||
setClientSetting(SETTINGS.FLOATING_PLAYER, !floatingPlayer);
|
||||
clearPlayingUri();
|
||||
}}
|
||||
checked={floatingPlayer}
|
||||
label={__('Floating video player')}
|
||||
|
|
|
@ -6,7 +6,14 @@ import { ipcRenderer, remote } from 'electron';
|
|||
import path from 'path';
|
||||
import * as ACTIONS from 'constants/action_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 { doFetchDaemonSettings } from 'redux/actions/settings';
|
||||
import { doCheckSubscriptionsInit } from 'redux/actions/subscriptions';
|
||||
|
@ -24,6 +31,7 @@ import {
|
|||
import { doAuthenticate } from 'lbryinc';
|
||||
import { lbrySettings as config, version as appVersion } from 'package.json';
|
||||
import { push } from 'connected-react-router';
|
||||
import analytics from 'analytics';
|
||||
|
||||
// @if TARGET='app'
|
||||
const { autoUpdater } = remote.require('electron-updater');
|
||||
|
@ -410,3 +418,18 @@ export function doToggleSearchExpanded() {
|
|||
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) {
|
||||
beginGetFile();
|
||||
return;
|
||||
|
|
|
@ -2,9 +2,11 @@ import * as ACTIONS from 'constants/action_types';
|
|||
// @if TARGET='app'
|
||||
import { shell } from 'electron';
|
||||
// @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 { goBack } from 'connected-react-router';
|
||||
import { doSetPlayingUri } from 'redux/actions/content';
|
||||
import { selectPlayingUri } from 'redux/selectors/content';
|
||||
|
||||
export function doOpenFileInFolder(path) {
|
||||
return () => {
|
||||
|
@ -47,14 +49,23 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) {
|
|||
};
|
||||
}
|
||||
|
||||
export function doDeleteFileAndMaybeGoBack(fileInfo, deleteFromComputer, abandonClaim) {
|
||||
return dispatch => {
|
||||
export function doDeleteFileAndMaybeGoBack(uri, deleteFromComputer, abandonClaim) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const playingUri = selectPlayingUri(state);
|
||||
const { outpoint } = makeSelectFileInfoForUri(uri)(state);
|
||||
const actions = [];
|
||||
actions.push(doHideModal());
|
||||
actions.push(doDeleteFile(fileInfo, deleteFromComputer, abandonClaim));
|
||||
dispatch(batchActions(...actions));
|
||||
if (abandonClaim) {
|
||||
dispatch(goBack());
|
||||
actions.push(doDeleteFile(outpoint, deleteFromComputer, abandonClaim));
|
||||
|
||||
if (playingUri === uri) {
|
||||
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
|
||||
const { isChannel } = parseURI(uri);
|
||||
let isChannel;
|
||||
try {
|
||||
({ isChannel } = parseURI(uri));
|
||||
} catch (e) {}
|
||||
|
||||
if (isChannel) {
|
||||
const uriWithPrefix = uri.startsWith('lbry://') ? uri : `lbry://${uri}`;
|
||||
return subscriptions.some(sub => sub.uri === uriWithPrefix);
|
||||
|
|
|
@ -94,6 +94,16 @@
|
|||
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 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
@ -85,11 +85,7 @@
|
|||
}
|
||||
|
||||
.content__loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -95,4 +95,11 @@
|
|||
.vjs-big-play-button {
|
||||
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 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
|
@ -12,7 +12,7 @@ export default function usePersistedState(key, firstTimeDefault) {
|
|||
parsedItem = JSON.parse(item);
|
||||
} catch (e) {}
|
||||
|
||||
if (parsedItem) {
|
||||
if (parsedItem !== undefined) {
|
||||
defaultValue = parsedItem;
|
||||
} else {
|
||||
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"
|
||||
zstd-codec "^0.1.1"
|
||||
|
||||
lbry-redux@lbryio/lbry-redux#05e70648e05c51c51710f6dd698a8e2219b54df2:
|
||||
lbry-redux@lbryio/lbry-redux#6005fa245a888e2de045d7e42411847de7943f52:
|
||||
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:
|
||||
mime "^2.4.4"
|
||||
proxy-polyfill "0.1.6"
|
||||
reselect "^3.0.0"
|
||||
uuid "^3.3.2"
|
||||
|
||||
lbryinc@lbryio/lbryinc#a93596c51c8fb0a226cb84df04c26a6bb60a45fb:
|
||||
lbryinc@lbryio/lbryinc#1ce266b3c52654190b955e9c869b8e302aa5c585:
|
||||
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:
|
||||
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.