copyable embed tag

This commit is contained in:
jessop 2019-10-29 12:23:56 -04:00 committed by Sean Yesmunt
parent 0ad4adceca
commit c0640012a9
9 changed files with 118 additions and 77 deletions

View file

@ -1,8 +1,8 @@
// @flow // @flow
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import * as React from 'react';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import React, { useRef } from 'react';
type Props = { type Props = {
copyable: string, copyable: string,
@ -11,58 +11,48 @@ type Props = {
label?: string, label?: string,
}; };
export default class CopyableText extends React.PureComponent<Props> { export default function CopyableText(props: Props) {
constructor() { const { copyable, doToast, snackMessage, label } = props;
super();
this.input = React.createRef(); const input = useRef();
(this: any).onFocus = this.onFocus.bind(this);
(this: any).copyToClipboard = this.copyToClipboard.bind(this);
}
input: { current: React.ElementRef<any> }; function copyToClipboard() {
const topRef = input.current;
copyToClipboard() {
const topRef = this.input.current;
if (topRef && topRef.input && topRef.input.current) { if (topRef && topRef.input && topRef.input.current) {
topRef.input.current.select(); topRef.input.current.select();
} }
document.execCommand('copy'); document.execCommand('copy');
} }
onFocus() { function onFocus() {
// We have to go a layer deep since the input is inside the form component // We have to go a layer deep since the input is inside the form component
const topRef = this.input.current; const topRef = input.current;
if (topRef && topRef.input && topRef.input.current) { if (topRef && topRef.input && topRef.input.current) {
topRef.input.current.select(); topRef.input.current.select();
} }
} }
render() { return (
const { copyable, doToast, snackMessage, label } = this.props; <FormField
type="text"
return ( className="form-field--copyable"
<FormField readOnly
type="text" label={label}
className="form-field--copyable" value={copyable || ''}
readOnly ref={input}
label={label} onFocus={onFocus}
value={copyable || ''} inputButton={
ref={this.input} <Button
onFocus={this.onFocus} button="inverse"
inputButton={ icon={ICONS.COPY}
<Button onClick={() => {
button="inverse" copyToClipboard();
icon={ICONS.COPY} doToast({
onClick={() => { message: snackMessage || __('Text copied'),
this.copyToClipboard(); });
doToast({ }}
message: snackMessage || __('Text copied'), />
}); }
}} />
/> );
}
/>
);
}
} }

View file

@ -0,0 +1,10 @@
import { connect } from 'react-redux';
import { doToast } from 'lbry-redux';
import EmbedArea from './view';
export default connect(
null,
{
doToast,
}
)(EmbedArea);

View file

@ -0,0 +1,63 @@
// @flow
import * as ICONS from 'constants/icons';
import { FormField } from 'component/common/form';
import Button from 'component/button';
import React, { useRef } from 'react';
import { generateStreamUrl } from 'util/lbrytv';
import { LBRY_TV_API } from 'config';
type Props = {
copyable: string,
snackMessage: ?string,
doToast: ({ message: string }) => void,
label?: string,
claim: Claim,
};
export default function EmbedArea(props: Props) {
const { doToast, snackMessage, label, claim } = props;
const { claim_id: claimId, name } = claim;
const input = useRef();
const streamUrl = generateStreamUrl(name, claimId, LBRY_TV_API);
let embedText = `<iframe width="560" height="315" src="${streamUrl}" allowfullscreen></iframe>`;
function copyToClipboard() {
const topRef = input.current;
if (topRef && topRef.input && topRef.input.current) {
topRef.input.current.select();
}
document.execCommand('copy');
}
function onFocus() {
// We have to go a layer deep since the input is inside the form component
const topRef = input.current;
if (topRef && topRef.input && topRef.input.current) {
topRef.input.current.select();
}
}
return (
<fieldset-section>
<FormField
type="textarea"
className="form-field--copyable"
label={label}
value={embedText || ''}
ref={input}
readOnly
onFocus={onFocus}
/>
<div className="card__actions card__actions--center">
<Button
icon={ICONS.COPY}
button="inverse"
onClick={() => {
copyToClipboard();
doToast({ message: snackMessage || 'Embed link copied' });
}}
/>
</div>
</fieldset-section>
);
}

View file

@ -17,7 +17,7 @@ export default function ShareButton(props: Props) {
button="alt" button="alt"
icon={ICONS.SHARE} icon={ICONS.SHARE}
label={__('Share')} label={__('Share')}
onClick={() => doOpenModal(MODALS.SOCIAL_SHARE, { uri, speechShareable: true, isChannel: true })} onClick={() => doOpenModal(MODALS.SOCIAL_SHARE, { uri, webShareable: true, isChannel: true })}
/> />
); );
} }

View file

@ -3,12 +3,12 @@ import * as ICONS from 'constants/icons';
import React from 'react'; import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import CopyableText from 'component/copyableText'; import CopyableText from 'component/copyableText';
import { DOMAIN } from 'config'; import EmbedArea from 'component/embedArea';
type Props = { type Props = {
claim: Claim, claim: Claim,
onDone: () => void, onDone: () => void,
speechShareable: boolean, webShareable: boolean,
isChannel: boolean, isChannel: boolean,
}; };
@ -28,42 +28,18 @@ class SocialShare extends React.PureComponent<Props> {
render() { render() {
const { claim } = this.props; const { claim } = this.props;
const { canonical_url: canonicalUrl, permanent_url: permanentUrl } = claim; const { canonical_url: canonicalUrl, permanent_url: permanentUrl } = claim;
const { speechShareable, onDone } = this.props; const { webShareable, onDone } = this.props;
const lbryTvPrefix = `${DOMAIN}/`;
const OPEN_URL = 'https://open.lbry.com/'; const OPEN_URL = 'https://open.lbry.com/';
const lbryUrl = canonicalUrl ? canonicalUrl.split('lbry://')[1] : permanentUrl.split('lbry://')[1]; const lbryUrl = canonicalUrl ? canonicalUrl.split('lbry://')[1] : permanentUrl.split('lbry://')[1];
const lbryWebUrl = lbryUrl.replace(/#/g, ':'); const lbryWebUrl = lbryUrl.replace(/#/g, ':');
const encodedLbryURL: string = `${OPEN_URL}${encodeURIComponent(lbryWebUrl)}`; const encodedLbryURL: string = `${OPEN_URL}${encodeURIComponent(lbryWebUrl)}`;
const lbryURL: string = `${OPEN_URL}${lbryWebUrl}`; const lbryURL: string = `${OPEN_URL}${lbryWebUrl}`;
const encodedLbryTvUrl = `${lbryTvPrefix}${encodeURIComponent(lbryWebUrl)}`;
const lbryTvUrl = `${lbryTvPrefix}${lbryWebUrl}`;
const shareOnFb = __('Share on Facebook'); const shareOnFb = __('Share on Facebook');
const shareOnTwitter = __('Share On Twitter'); const shareOnTwitter = __('Share On Twitter');
return ( return (
<React.Fragment> <React.Fragment>
{speechShareable && (
<div>
<CopyableText label={__('Web link')} copyable={lbryTvUrl} />
<div className="card__actions card__actions--center">
<Button
icon={ICONS.FACEBOOK}
button="link"
description={shareOnFb}
href={`https://facebook.com/sharer/sharer.php?u=${encodedLbryTvUrl}`}
/>
<Button
icon={ICONS.TWITTER}
button="link"
description={shareOnTwitter}
href={`https://twitter.com/intent/tweet?text=${encodedLbryTvUrl}`}
/>
<Button icon={ICONS.WEB} button="link" description={__('View on lbry.tv')} href={`${lbryTvUrl}`} />
</div>
</div>
)}
<CopyableText label={__('LBRY App Link')} copyable={lbryURL} noSnackbar /> <CopyableText label={__('LBRY App Link')} copyable={lbryURL} noSnackbar />
<div className="card__actions card__actions--center"> <div className="card__actions card__actions--center">
<Button <Button
@ -79,6 +55,8 @@ class SocialShare extends React.PureComponent<Props> {
href={`https://twitter.com/intent/tweet?text=${encodedLbryURL}`} href={`https://twitter.com/intent/tweet?text=${encodedLbryURL}`}
/> />
</div> </div>
{webShareable && <EmbedArea label={__('Embedded')} claim={claim} noSnackbar />}
{!webShareable && <p className={'help'}>{__('Paid content cannot be embedded')}</p>}
<div className="card__actions"> <div className="card__actions">
<Button button="link" label={__('Done')} onClick={onDone} /> <Button button="link" label={__('Done')} onClick={onDone} />
</div> </div>

View file

@ -6,16 +6,16 @@ import SocialShare from 'component/socialShare';
type Props = { type Props = {
closeModal: () => void, closeModal: () => void,
uri: string, uri: string,
speechShareable: boolean, webShareable: boolean,
isChannel: boolean, isChannel: boolean,
}; };
class ModalSocialShare extends React.PureComponent<Props> { class ModalSocialShare extends React.PureComponent<Props> {
render() { render() {
const { closeModal, uri, speechShareable, isChannel } = this.props; const { closeModal, uri, webShareable, isChannel } = this.props;
return ( return (
<Modal isOpen onAborted={closeModal} type="custom" title={__('Share')}> <Modal isOpen onAborted={closeModal} type="custom" title={__('Share')}>
<SocialShare uri={uri} onDone={closeModal} speechShareable={speechShareable} isChannel={isChannel} /> <SocialShare uri={uri} onDone={closeModal} webShareable={webShareable} isChannel={isChannel} />
</Modal> </Modal>
); );
} }

View file

@ -118,7 +118,7 @@ class FilePage extends React.Component<Props> {
const { signing_channel: signingChannel } = claim; const { signing_channel: signingChannel } = claim;
const channelName = signingChannel && signingChannel.name; const channelName = signingChannel && signingChannel.name;
const isRewardContent = (rewardedContentClaimIds || []).includes(claim.claim_id); const isRewardContent = (rewardedContentClaimIds || []).includes(claim.claim_id);
const speechShareable = const webShareable =
costInfo && costInfo.cost === 0 && contentType && ['video', 'image', 'audio'].includes(contentType.split('/')[0]); costInfo && costInfo.cost === 0 && contentType && ['video', 'image', 'audio'].includes(contentType.split('/')[0]);
// We want to use the short form uri for editing // 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 // This is what the user is used to seeing, they don't care about the claim id
@ -203,7 +203,7 @@ class FilePage extends React.Component<Props> {
button="alt" button="alt"
icon={icons.SHARE} icon={icons.SHARE}
label={__('Share')} label={__('Share')}
onClick={() => openModal(MODALS.SOCIAL_SHARE, { uri, speechShareable })} onClick={() => openModal(MODALS.SOCIAL_SHARE, { uri, webShareable })}
/> />
</div> </div>

View file

@ -1,5 +1,5 @@
function generateStreamUrl(claimName, claimId) { function generateStreamUrl(claimName, claimId, apiUrl) {
const prefix = process.env.SDK_API_URL; const prefix = process.env.SDK_API_URL || apiUrl;
return `${prefix}/content/claims/${claimName}/${claimId}/stream`; return `${prefix}/content/claims/${claimName}/${claimId}/stream`;
} }

View file

@ -828,8 +828,8 @@
"To enable this feature, check 'Save Password' the next time you start the app.": "To enable this feature, check 'Save Password' the next time you start the app.", "To enable this feature, check 'Save Password' the next time you start the app.": "To enable this feature, check 'Save Password' the next time you start the app.",
"An email address is required to sync your account.": "An email address is required to sync your account.", "An email address is required to sync your account.": "An email address is required to sync your account.",
"Sign Out": "Sign Out", "Sign Out": "Sign Out",
"Follow more tags": "Follow more tags",
"Portuguese": "Portuguese", "Portuguese": "Portuguese",
"Follow more tags": "Follow more tags",
"Sign In to LBRY": "Sign In to LBRY", "Sign In to LBRY": "Sign In to LBRY",
"Check Your Email": "Check Your Email", "Check Your Email": "Check Your Email",
"sign in": "sign in", "sign in": "sign in",