copyable embed tag
This commit is contained in:
parent
0ad4adceca
commit
c0640012a9
9 changed files with 118 additions and 77 deletions
|
@ -1,8 +1,8 @@
|
|||
// @flow
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as React from 'react';
|
||||
import { FormField } from 'component/common/form';
|
||||
import Button from 'component/button';
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
type Props = {
|
||||
copyable: string,
|
||||
|
@ -11,58 +11,48 @@ type Props = {
|
|||
label?: string,
|
||||
};
|
||||
|
||||
export default class CopyableText extends React.PureComponent<Props> {
|
||||
constructor() {
|
||||
super();
|
||||
export default function CopyableText(props: Props) {
|
||||
const { copyable, doToast, snackMessage, label } = props;
|
||||
|
||||
this.input = React.createRef();
|
||||
(this: any).onFocus = this.onFocus.bind(this);
|
||||
(this: any).copyToClipboard = this.copyToClipboard.bind(this);
|
||||
}
|
||||
const input = useRef();
|
||||
|
||||
input: { current: React.ElementRef<any> };
|
||||
|
||||
copyToClipboard() {
|
||||
const topRef = this.input.current;
|
||||
function copyToClipboard() {
|
||||
const topRef = input.current;
|
||||
if (topRef && topRef.input && topRef.input.current) {
|
||||
topRef.input.current.select();
|
||||
}
|
||||
document.execCommand('copy');
|
||||
}
|
||||
|
||||
onFocus() {
|
||||
function onFocus() {
|
||||
// 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) {
|
||||
topRef.input.current.select();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { copyable, doToast, snackMessage, label } = this.props;
|
||||
|
||||
return (
|
||||
<FormField
|
||||
type="text"
|
||||
className="form-field--copyable"
|
||||
readOnly
|
||||
label={label}
|
||||
value={copyable || ''}
|
||||
ref={this.input}
|
||||
onFocus={this.onFocus}
|
||||
inputButton={
|
||||
<Button
|
||||
button="inverse"
|
||||
icon={ICONS.COPY}
|
||||
onClick={() => {
|
||||
this.copyToClipboard();
|
||||
doToast({
|
||||
message: snackMessage || __('Text copied'),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<FormField
|
||||
type="text"
|
||||
className="form-field--copyable"
|
||||
readOnly
|
||||
label={label}
|
||||
value={copyable || ''}
|
||||
ref={input}
|
||||
onFocus={onFocus}
|
||||
inputButton={
|
||||
<Button
|
||||
button="inverse"
|
||||
icon={ICONS.COPY}
|
||||
onClick={() => {
|
||||
copyToClipboard();
|
||||
doToast({
|
||||
message: snackMessage || __('Text copied'),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
10
src/ui/component/embedArea/index.js
Normal file
10
src/ui/component/embedArea/index.js
Normal 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);
|
63
src/ui/component/embedArea/view.jsx
Normal file
63
src/ui/component/embedArea/view.jsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -17,7 +17,7 @@ export default function ShareButton(props: Props) {
|
|||
button="alt"
|
||||
icon={ICONS.SHARE}
|
||||
label={__('Share')}
|
||||
onClick={() => doOpenModal(MODALS.SOCIAL_SHARE, { uri, speechShareable: true, isChannel: true })}
|
||||
onClick={() => doOpenModal(MODALS.SOCIAL_SHARE, { uri, webShareable: true, isChannel: true })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@ import * as ICONS from 'constants/icons';
|
|||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import CopyableText from 'component/copyableText';
|
||||
import { DOMAIN } from 'config';
|
||||
import EmbedArea from 'component/embedArea';
|
||||
|
||||
type Props = {
|
||||
claim: Claim,
|
||||
onDone: () => void,
|
||||
speechShareable: boolean,
|
||||
webShareable: boolean,
|
||||
isChannel: boolean,
|
||||
};
|
||||
|
||||
|
@ -28,42 +28,18 @@ class SocialShare extends React.PureComponent<Props> {
|
|||
render() {
|
||||
const { claim } = this.props;
|
||||
const { canonical_url: canonicalUrl, permanent_url: permanentUrl } = claim;
|
||||
const { speechShareable, onDone } = this.props;
|
||||
const lbryTvPrefix = `${DOMAIN}/`;
|
||||
const { webShareable, onDone } = this.props;
|
||||
const OPEN_URL = 'https://open.lbry.com/';
|
||||
const lbryUrl = canonicalUrl ? canonicalUrl.split('lbry://')[1] : permanentUrl.split('lbry://')[1];
|
||||
const lbryWebUrl = lbryUrl.replace(/#/g, ':');
|
||||
const encodedLbryURL: string = `${OPEN_URL}${encodeURIComponent(lbryWebUrl)}`;
|
||||
const lbryURL: string = `${OPEN_URL}${lbryWebUrl}`;
|
||||
const encodedLbryTvUrl = `${lbryTvPrefix}${encodeURIComponent(lbryWebUrl)}`;
|
||||
const lbryTvUrl = `${lbryTvPrefix}${lbryWebUrl}`;
|
||||
|
||||
const shareOnFb = __('Share on Facebook');
|
||||
const shareOnTwitter = __('Share On Twitter');
|
||||
|
||||
return (
|
||||
<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 />
|
||||
<div className="card__actions card__actions--center">
|
||||
<Button
|
||||
|
@ -79,6 +55,8 @@ class SocialShare extends React.PureComponent<Props> {
|
|||
href={`https://twitter.com/intent/tweet?text=${encodedLbryURL}`}
|
||||
/>
|
||||
</div>
|
||||
{webShareable && <EmbedArea label={__('Embedded')} claim={claim} noSnackbar />}
|
||||
{!webShareable && <p className={'help'}>{__('Paid content cannot be embedded')}</p>}
|
||||
<div className="card__actions">
|
||||
<Button button="link" label={__('Done')} onClick={onDone} />
|
||||
</div>
|
||||
|
|
|
@ -6,16 +6,16 @@ import SocialShare from 'component/socialShare';
|
|||
type Props = {
|
||||
closeModal: () => void,
|
||||
uri: string,
|
||||
speechShareable: boolean,
|
||||
webShareable: boolean,
|
||||
isChannel: boolean,
|
||||
};
|
||||
|
||||
class ModalSocialShare extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const { closeModal, uri, speechShareable, isChannel } = this.props;
|
||||
const { closeModal, uri, webShareable, isChannel } = this.props;
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ class FilePage extends React.Component<Props> {
|
|||
const { signing_channel: signingChannel } = claim;
|
||||
const channelName = signingChannel && signingChannel.name;
|
||||
const isRewardContent = (rewardedContentClaimIds || []).includes(claim.claim_id);
|
||||
const speechShareable =
|
||||
const webShareable =
|
||||
costInfo && costInfo.cost === 0 && contentType && ['video', 'image', 'audio'].includes(contentType.split('/')[0]);
|
||||
// We want to use the short form uri for editing
|
||||
// This is what the user is used to seeing, they don't care about the claim id
|
||||
|
@ -203,7 +203,7 @@ class FilePage extends React.Component<Props> {
|
|||
button="alt"
|
||||
icon={icons.SHARE}
|
||||
label={__('Share')}
|
||||
onClick={() => openModal(MODALS.SOCIAL_SHARE, { uri, speechShareable })}
|
||||
onClick={() => openModal(MODALS.SOCIAL_SHARE, { uri, webShareable })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
function generateStreamUrl(claimName, claimId) {
|
||||
const prefix = process.env.SDK_API_URL;
|
||||
function generateStreamUrl(claimName, claimId, apiUrl) {
|
||||
const prefix = process.env.SDK_API_URL || apiUrl;
|
||||
return `${prefix}/content/claims/${claimName}/${claimId}/stream`;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.",
|
||||
"An email address is required to sync your account.": "An email address is required to sync your account.",
|
||||
"Sign Out": "Sign Out",
|
||||
"Follow more tags": "Follow more tags",
|
||||
"Portuguese": "Portuguese",
|
||||
"Follow more tags": "Follow more tags",
|
||||
"Sign In to LBRY": "Sign In to LBRY",
|
||||
"Check Your Email": "Check Your Email",
|
||||
"sign in": "sign in",
|
||||
|
|
Loading…
Add table
Reference in a new issue