Merge branch 'master' of github.com:lbryio/lbry-desktop
This commit is contained in:
commit
71119ecdd7
33 changed files with 438 additions and 95 deletions
|
@ -1,16 +1,10 @@
|
||||||
const { getHtml } = require('./html');
|
const { getHtml } = require('./html');
|
||||||
const { generateStreamUrl, generateDownloadUrl } = require('../../ui/util/lbrytv');
|
const { generateDownloadUrl } = require('../../ui/util/lbrytv');
|
||||||
|
const { LBRY_TV_API } = require('../../config');
|
||||||
const Router = require('@koa/router');
|
const Router = require('@koa/router');
|
||||||
const send = require('koa-send');
|
|
||||||
|
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
router.get(`/$/embed/:claimName/:claimId`, async ctx => {
|
|
||||||
const { claimName, claimId } = ctx.params;
|
|
||||||
const streamUrl = generateStreamUrl(claimName, claimId);
|
|
||||||
ctx.redirect(streamUrl);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get(`/$/download/:claimName/:claimId`, async ctx => {
|
router.get(`/$/download/:claimName/:claimId`, async ctx => {
|
||||||
const { claimName, claimId } = ctx.params;
|
const { claimName, claimId } = ctx.params;
|
||||||
const downloadUrl = generateDownloadUrl(claimName, claimId);
|
const downloadUrl = generateDownloadUrl(claimName, claimId);
|
||||||
|
|
|
@ -157,6 +157,7 @@
|
||||||
"Details": "Details",
|
"Details": "Details",
|
||||||
"Transaction": "Transaction",
|
"Transaction": "Transaction",
|
||||||
"Date": "Date",
|
"Date": "Date",
|
||||||
|
"Abandon Support": "Abandon Support",
|
||||||
"Abandon Claim": "Abandon Claim",
|
"Abandon Claim": "Abandon Claim",
|
||||||
"Find New Tags To Follow": "Find New Tags To Follow",
|
"Find New Tags To Follow": "Find New Tags To Follow",
|
||||||
"Aw shucks!": "Aw shucks!",
|
"Aw shucks!": "Aw shucks!",
|
||||||
|
|
25
ui/component/autoplayCountdown/index.js
Normal file
25
ui/component/autoplayCountdown/index.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import * as SETTINGS from 'constants/settings';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||||
|
import { makeSelectNextUnplayedRecommended } from 'redux/selectors/content';
|
||||||
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
|
import { doSetPlayingUri } from 'redux/actions/content';
|
||||||
|
import RecommendedVideos from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => {
|
||||||
|
const nextRecommendedUri = makeSelectNextUnplayedRecommended(props.uri)(state);
|
||||||
|
return {
|
||||||
|
nextRecommendedUri,
|
||||||
|
nextRecommendedClaim: makeSelectClaimForUri(nextRecommendedUri)(state),
|
||||||
|
autoplay: makeSelectClientSetting(SETTINGS.AUTOPLAY)(state),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
setPlayingUri: uri => dispatch(doSetPlayingUri(uri)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(RecommendedVideos);
|
76
ui/component/autoplayCountdown/view.jsx
Normal file
76
ui/component/autoplayCountdown/view.jsx
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import UriIndicator from 'component/uriIndicator';
|
||||||
|
import I18nMessage from 'component/i18nMessage';
|
||||||
|
import { formatLbryUrlForWeb } from 'util/url';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
history: { push: string => void },
|
||||||
|
nextRecommendedClaim: ?StreamClaim,
|
||||||
|
nextRecommendedUri: string,
|
||||||
|
setPlayingUri: (string | null) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
function AutoplayCountdown(props: Props) {
|
||||||
|
const {
|
||||||
|
nextRecommendedUri,
|
||||||
|
nextRecommendedClaim,
|
||||||
|
setPlayingUri,
|
||||||
|
history: { push },
|
||||||
|
} = props;
|
||||||
|
const nextTitle = nextRecommendedClaim && nextRecommendedClaim.value && nextRecommendedClaim.value.title;
|
||||||
|
const [timer, setTimer] = React.useState(5);
|
||||||
|
const [timerCanceled, setTimerCanceled] = React.useState(false);
|
||||||
|
|
||||||
|
let navigateUrl;
|
||||||
|
if (nextTitle) {
|
||||||
|
navigateUrl = formatLbryUrlForWeb(nextRecommendedUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
let interval;
|
||||||
|
if (!timerCanceled) {
|
||||||
|
interval = setInterval(() => {
|
||||||
|
const newTime = timer - 1;
|
||||||
|
if (newTime === 0) {
|
||||||
|
// Set the playingUri to null so the app doesn't try to make a floating window with the video that just finished
|
||||||
|
setPlayingUri(null);
|
||||||
|
push(navigateUrl);
|
||||||
|
} else {
|
||||||
|
setTimer(timer - 1);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, [timer, navigateUrl, push, timerCanceled, setPlayingUri, nextRecommendedUri]);
|
||||||
|
|
||||||
|
if (timerCanceled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="video-overlay__wrapper">
|
||||||
|
<div className="video-overlay__subtitle">
|
||||||
|
<I18nMessage tokens={{ channel: <UriIndicator link uri={nextRecommendedUri} /> }}>
|
||||||
|
Up Next by %channel%
|
||||||
|
</I18nMessage>
|
||||||
|
</div>
|
||||||
|
<div className="video-overlay__title">{nextTitle}</div>
|
||||||
|
|
||||||
|
<div className="video-overlay__actions">
|
||||||
|
<div className="video-overlay__subtitle">
|
||||||
|
{__('Playing in %seconds_left% seconds', { seconds_left: timer })}
|
||||||
|
</div>
|
||||||
|
<div className="section__actions--centered">
|
||||||
|
<Button label={__('Cancel')} button="link" onClick={() => setTimerCanceled(true)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withRouter(AutoplayCountdown);
|
|
@ -147,6 +147,7 @@ class ChannelCreate extends React.PureComponent<Props, State> {
|
||||||
error={newChannelBidError}
|
error={newChannelBidError}
|
||||||
value={newChannelBid}
|
value={newChannelBid}
|
||||||
onChange={event => this.handleNewChannelBidChange(parseFloat(event.target.value))}
|
onChange={event => this.handleNewChannelBidChange(parseFloat(event.target.value))}
|
||||||
|
onWheel={e => e.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
<div className="card__actions">
|
<div className="card__actions">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -116,20 +116,20 @@ function ClaimListDiscover(props: Props) {
|
||||||
// For more than 20, drop it down to 6 months
|
// For more than 20, drop it down to 6 months
|
||||||
// This helps with timeout issues for users that are following a ton of stuff
|
// This helps with timeout issues for users that are following a ton of stuff
|
||||||
// https://github.com/lbryio/lbry-sdk/issues/2420
|
// https://github.com/lbryio/lbry-sdk/issues/2420
|
||||||
if (options.channel_ids.length > 10 || options.any_tags.length > 10) {
|
if (options.channel_ids.length > 20 || options.any_tags.length > 20) {
|
||||||
options.release_time = `>${Math.floor(
|
|
||||||
moment()
|
|
||||||
.subtract(1, TIME_YEAR)
|
|
||||||
.startOf('week')
|
|
||||||
.unix()
|
|
||||||
)}`;
|
|
||||||
} else if (options.channel_ids.length > 20 || options.any_tags.length > 20) {
|
|
||||||
options.release_time = `>${Math.floor(
|
options.release_time = `>${Math.floor(
|
||||||
moment()
|
moment()
|
||||||
.subtract(6, TIME_MONTH)
|
.subtract(6, TIME_MONTH)
|
||||||
.startOf('week')
|
.startOf('week')
|
||||||
.unix()
|
.unix()
|
||||||
)}`;
|
)}`;
|
||||||
|
} else if (options.channel_ids.length > 10 || options.any_tags.length > 10) {
|
||||||
|
options.release_time = `>${Math.floor(
|
||||||
|
moment()
|
||||||
|
.subtract(1, TIME_YEAR)
|
||||||
|
.startOf('week')
|
||||||
|
.unix()
|
||||||
|
)}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,8 +53,12 @@ function ClaimPreviewTile(props: Props) {
|
||||||
} = props;
|
} = props;
|
||||||
const shouldFetch = claim === undefined;
|
const shouldFetch = claim === undefined;
|
||||||
const thumbnailUrl = useGetThumbnail(uri, claim, streamingUrl, getFile, placeholder) || thumbnail;
|
const thumbnailUrl = useGetThumbnail(uri, claim, streamingUrl, getFile, placeholder) || thumbnail;
|
||||||
const navigateUrl = uri ? formatLbryUrlForWeb(uri) : undefined;
|
|
||||||
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
||||||
|
const navigateUrl = uri ? formatLbryUrlForWeb(uri) : undefined;
|
||||||
|
const navLinkProps = {
|
||||||
|
to: navigateUrl,
|
||||||
|
onClick: e => e.stopPropagation(),
|
||||||
|
};
|
||||||
|
|
||||||
let isChannel;
|
let isChannel;
|
||||||
let isValid = false;
|
let isValid = false;
|
||||||
|
@ -142,10 +146,10 @@ function ClaimPreviewTile(props: Props) {
|
||||||
'claim-preview--channel': isChannel,
|
'claim-preview--channel': isChannel,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<NavLink to={navigateUrl}>
|
<NavLink {...navLinkProps}>
|
||||||
<FileThumbnail thumbnail={thumbnailUrl} />
|
<FileThumbnail thumbnail={thumbnailUrl} />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink to={navigateUrl}>
|
<NavLink {...navLinkProps}>
|
||||||
<h2 className="claim-tile__title">
|
<h2 className="claim-tile__title">
|
||||||
<TruncatedText text={title || (claim && claim.name)} lines={2} />
|
<TruncatedText text={title || (claim && claim.name)} lines={2} />
|
||||||
</h2>
|
</h2>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doToast } from 'lbry-redux';
|
import { doToast } from 'lbry-redux';
|
||||||
import EmbedArea from './view';
|
import EmbedTextArea from './view';
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
doToast,
|
doToast,
|
||||||
}
|
}
|
||||||
)(EmbedArea);
|
)(EmbedTextArea);
|
|
@ -13,7 +13,7 @@ type Props = {
|
||||||
claim: Claim,
|
claim: Claim,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function EmbedArea(props: Props) {
|
export default function EmbedTextArea(props: Props) {
|
||||||
const { doToast, snackMessage, label, claim } = props;
|
const { doToast, snackMessage, label, claim } = props;
|
||||||
const { claim_id: claimId, name } = claim;
|
const { claim_id: claimId, name } = claim;
|
||||||
const input = useRef();
|
const input = useRef();
|
|
@ -8,13 +8,14 @@ import {
|
||||||
makeSelectDownloadPathForUri,
|
makeSelectDownloadPathForUri,
|
||||||
makeSelectFileNameForUri,
|
makeSelectFileNameForUri,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { THEME, AUTOPLAY } from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import { makeSelectNextUnplayedRecommended, makeSelectIsText } from 'redux/selectors/content';
|
import { makeSelectIsText } from 'redux/selectors/content';
|
||||||
|
import { doSetPlayingUri } from 'redux/actions/content';
|
||||||
import FileRender from './view';
|
import FileRender from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
currentTheme: makeSelectClientSetting(THEME)(state),
|
currentTheme: makeSelectClientSetting(SETTINGS.THEME)(state),
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
mediaType: makeSelectMediaTypeForUri(props.uri)(state),
|
mediaType: makeSelectMediaTypeForUri(props.uri)(state),
|
||||||
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
||||||
|
@ -22,9 +23,15 @@ const select = (state, props) => ({
|
||||||
downloadPath: makeSelectDownloadPathForUri(props.uri)(state),
|
downloadPath: makeSelectDownloadPathForUri(props.uri)(state),
|
||||||
fileName: makeSelectFileNameForUri(props.uri)(state),
|
fileName: makeSelectFileNameForUri(props.uri)(state),
|
||||||
streamingUrl: makeSelectStreamingUrlForUri(props.uri)(state),
|
streamingUrl: makeSelectStreamingUrlForUri(props.uri)(state),
|
||||||
autoplay: makeSelectClientSetting(AUTOPLAY)(state),
|
autoplay: makeSelectClientSetting(SETTINGS.AUTOPLAY)(state),
|
||||||
nextUnplayed: makeSelectNextUnplayedRecommended(props.uri)(state),
|
|
||||||
isText: makeSelectIsText(props.uri)(state),
|
isText: makeSelectIsText(props.uri)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select)(FileRender);
|
const perform = dispatch => ({
|
||||||
|
setPlayingUri: uri => dispatch(doSetPlayingUri(uri)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(FileRender);
|
||||||
|
|
|
@ -8,7 +8,7 @@ import ImageViewer from 'component/viewers/imageViewer';
|
||||||
import AppViewer from 'component/viewers/appViewer';
|
import AppViewer from 'component/viewers/appViewer';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import { formatLbryUrlForWeb } from 'util/url';
|
import AutoplayCountdown from 'component/autoplayCountdown';
|
||||||
// @if TARGET='web'
|
// @if TARGET='web'
|
||||||
import { generateStreamUrl } from 'util/lbrytv';
|
import { generateStreamUrl } from 'util/lbrytv';
|
||||||
// @endif
|
// @endif
|
||||||
|
@ -31,23 +31,35 @@ type Props = {
|
||||||
mediaType: string,
|
mediaType: string,
|
||||||
isText: true,
|
isText: true,
|
||||||
streamingUrl: string,
|
streamingUrl: string,
|
||||||
|
embedded?: boolean,
|
||||||
contentType: string,
|
contentType: string,
|
||||||
claim: StreamClaim,
|
claim: StreamClaim,
|
||||||
currentTheme: string,
|
currentTheme: string,
|
||||||
downloadPath: string,
|
downloadPath: string,
|
||||||
fileName: string,
|
fileName: string,
|
||||||
autoplay: boolean,
|
autoplay: boolean,
|
||||||
nextFileToPlay: string,
|
setPlayingUri: (string | null) => void,
|
||||||
nextUnplayed: string,
|
currentlyFloating: boolean,
|
||||||
history: { push: string => void },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class FileRender extends React.PureComponent<Props> {
|
type State = {
|
||||||
|
showAutoplayCountdown: boolean,
|
||||||
|
showEmbededMessage: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileRender extends React.PureComponent<Props, State> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
showAutoplayCountdown: false,
|
||||||
|
showEmbededMessage: false,
|
||||||
|
};
|
||||||
|
|
||||||
(this: any).escapeListener = this.escapeListener.bind(this);
|
(this: any).escapeListener = this.escapeListener.bind(this);
|
||||||
(this: any).onEndedCb = this.onEndedCb.bind(this);
|
(this: any).onEndedAutoplay = this.onEndedAutoplay.bind(this);
|
||||||
|
(this: any).onEndedEmbedded = this.onEndedEmbedded.bind(this);
|
||||||
|
(this: any).getOnEndedCb = this.getOnEndedCb.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -72,11 +84,29 @@ class FileRender extends React.PureComponent<Props> {
|
||||||
remote.getCurrentWindow().setFullScreen(false);
|
remote.getCurrentWindow().setFullScreen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEndedCb() {
|
getOnEndedCb() {
|
||||||
const { autoplay, nextUnplayed, history } = this.props;
|
const { setPlayingUri, currentlyFloating, embedded } = this.props;
|
||||||
if (autoplay && nextUnplayed) {
|
|
||||||
history.push(formatLbryUrlForWeb(nextUnplayed));
|
if (embedded) {
|
||||||
|
return this.onEndedEmbedded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!currentlyFloating) {
|
||||||
|
return this.onEndedAutoplay;
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => setPlayingUri(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
onEndedAutoplay() {
|
||||||
|
const { autoplay } = this.props;
|
||||||
|
if (autoplay) {
|
||||||
|
this.setState({ showAutoplayCountdown: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onEndedEmbedded() {
|
||||||
|
this.setState({ showEmbededMessage: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
renderViewer() {
|
renderViewer() {
|
||||||
|
@ -98,8 +128,8 @@ class FileRender extends React.PureComponent<Props> {
|
||||||
application: <AppViewer uri={uri} />,
|
application: <AppViewer uri={uri} />,
|
||||||
// @endif
|
// @endif
|
||||||
|
|
||||||
video: <VideoViewer uri={uri} source={source} contentType={contentType} onEndedCB={this.onEndedCb} />,
|
video: <VideoViewer uri={uri} source={source} contentType={contentType} onEndedCB={this.getOnEndedCb()} />,
|
||||||
audio: <VideoViewer uri={uri} source={source} contentType={contentType} onEndedCB={this.onEndedCb} />,
|
audio: <VideoViewer uri={uri} source={source} contentType={contentType} onEndedCB={this.getOnEndedCb()} />,
|
||||||
image: <ImageViewer uri={uri} source={source} />,
|
image: <ImageViewer uri={uri} source={source} />,
|
||||||
// Add routes to viewer...
|
// Add routes to viewer...
|
||||||
};
|
};
|
||||||
|
@ -107,10 +137,10 @@ class FileRender extends React.PureComponent<Props> {
|
||||||
// Supported contentTypes
|
// Supported contentTypes
|
||||||
const contentTypes = {
|
const contentTypes = {
|
||||||
'application/x-ext-mkv': (
|
'application/x-ext-mkv': (
|
||||||
<VideoViewer uri={uri} source={source} contentType={contentType} onEndedCB={this.onEndedCb} />
|
<VideoViewer uri={uri} source={source} contentType={contentType} onEndedCB={this.getOnEndedCb()} />
|
||||||
),
|
),
|
||||||
'video/x-matroska': (
|
'video/x-matroska': (
|
||||||
<VideoViewer uri={uri} source={source} contentType={contentType} onEndedCB={this.onEndedCb} />
|
<VideoViewer uri={uri} source={source} contentType={contentType} onEndedCB={this.getOnEndedCb()} />
|
||||||
),
|
),
|
||||||
'application/pdf': <PdfViewer source={downloadPath || source} />,
|
'application/pdf': <PdfViewer source={downloadPath || source} />,
|
||||||
'text/html': <HtmlViewer source={downloadPath || source} />,
|
'text/html': <HtmlViewer source={downloadPath || source} />,
|
||||||
|
@ -186,12 +216,30 @@ class FileRender extends React.PureComponent<Props> {
|
||||||
// Return viewer
|
// Return viewer
|
||||||
return viewer || unsupported;
|
return viewer || unsupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isText } = this.props;
|
const { isText, uri, currentlyFloating, embedded } = this.props;
|
||||||
|
const { showAutoplayCountdown, showEmbededMessage } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames('file-render', { 'file-render--document': isText })}>
|
<div
|
||||||
|
className={classnames({
|
||||||
|
'file-render': !embedded,
|
||||||
|
'file-render--document': isText && !embedded,
|
||||||
|
'file-render__embed': embedded,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{embedded && showEmbededMessage && (
|
||||||
|
<div className="video-overlay__wrapper">
|
||||||
|
<div className="video-overlay__title">{__('View More on lbry.tv')}</div>
|
||||||
|
|
||||||
|
<div className="video-overlay__actions">
|
||||||
|
<div className="section__actions--centered">
|
||||||
|
<Button label={__('Explore')} button="primary" href="https://lbry.tv?src=embed" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!currentlyFloating && showAutoplayCountdown && <AutoplayCountdown uri={uri} />}
|
||||||
<Suspense fallback={<div />}>{this.renderViewer()}</Suspense>
|
<Suspense fallback={<div />}>{this.renderViewer()}</Suspense>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -175,7 +175,11 @@ export default function FileViewer(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isReadyToPlay ? <FileRender uri={uri} /> : <LoadingScreen status={loadingMessage} />}
|
{isReadyToPlay ? (
|
||||||
|
<FileRender currentlyFloating={!inline} uri={uri} />
|
||||||
|
) : (
|
||||||
|
<LoadingScreen status={loadingMessage} />
|
||||||
|
)}
|
||||||
{!inline && (
|
{!inline && (
|
||||||
<div className="draggable content__info">
|
<div className="draggable content__info">
|
||||||
<div className="claim-preview-title" title={title || uri}>
|
<div className="claim-preview-title" title={title || uri}>
|
||||||
|
|
|
@ -165,14 +165,6 @@ const Header = (props: Props) => {
|
||||||
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
<Icon aria-hidden icon={ICONS.CHANNEL} />
|
||||||
{__('Channels')}
|
{__('Channels')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
{/* @if TARGET='app' */}
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.LIBRARY}`)}>
|
|
||||||
<Icon aria-hidden icon={ICONS.LIBRARY} />
|
|
||||||
{__('Library')}
|
|
||||||
</MenuItem>
|
|
||||||
{/* @endif */}
|
|
||||||
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.REWARDS}`)}>
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.REWARDS}`)}>
|
||||||
<Icon aria-hidden icon={ICONS.FEATURED} />
|
<Icon aria-hidden icon={ICONS.FEATURED} />
|
||||||
{__('Rewards')}
|
{__('Rewards')}
|
||||||
|
|
|
@ -113,6 +113,7 @@ function PublishName(props: Props) {
|
||||||
error={bidError}
|
error={bidError}
|
||||||
disabled={!name}
|
disabled={!name}
|
||||||
onChange={event => updatePublishForm({ bid: parseFloat(event.target.value) })}
|
onChange={event => updatePublishForm({ bid: parseFloat(event.target.value) })}
|
||||||
|
onWheel={e => e.stopPropagation()}
|
||||||
helper={
|
helper={
|
||||||
<BidHelpText
|
<BidHelpText
|
||||||
uri={'lbry://' + name}
|
uri={'lbry://' + name}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import FourOhFourPage from 'page/fourOhFour';
|
||||||
import SignInPage from 'page/signIn';
|
import SignInPage from 'page/signIn';
|
||||||
import SignInVerifyPage from 'page/signInVerify';
|
import SignInVerifyPage from 'page/signInVerify';
|
||||||
import ChannelsPage from 'page/channels';
|
import ChannelsPage from 'page/channels';
|
||||||
|
import EmbedWrapperPage from 'page/embedWrapper';
|
||||||
|
|
||||||
// Tell the browser we are handling scroll restoration
|
// Tell the browser we are handling scroll restoration
|
||||||
if ('scrollRestoration' in history) {
|
if ('scrollRestoration' in history) {
|
||||||
|
@ -105,6 +106,9 @@ function AppRouter(props: Props) {
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.CHANNELS}`} component={ChannelsPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.CHANNELS}`} component={ChannelsPage} />
|
||||||
|
|
||||||
|
<Route path={`/$/${PAGES.EMBED}/:claimName`} exact component={EmbedWrapperPage} />
|
||||||
|
<Route path={`/$/${PAGES.EMBED}/:claimName/:claimId`} exact component={EmbedWrapperPage} />
|
||||||
|
|
||||||
{/* Below need to go at the end to make sure we don't match any of our pages first */}
|
{/* Below need to go at the end to make sure we don't match any of our pages first */}
|
||||||
<Route path="/:claimName" exact component={ShowPage} />
|
<Route path="/:claimName" exact component={ShowPage} />
|
||||||
<Route path="/:claimName/:streamName" exact component={ShowPage} />
|
<Route path="/:claimName/:streamName" exact component={ShowPage} />
|
||||||
|
|
|
@ -112,6 +112,11 @@ function SideNavigation(props: Props) {
|
||||||
{
|
{
|
||||||
...buildLink(PAGES.DISCOVER, __('All Content'), ICONS.DISCOVER),
|
...buildLink(PAGES.DISCOVER, __('All Content'), ICONS.DISCOVER),
|
||||||
},
|
},
|
||||||
|
// @if TARGET='app'
|
||||||
|
{
|
||||||
|
...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY),
|
||||||
|
},
|
||||||
|
// @endif
|
||||||
].map(linkProps => (
|
].map(linkProps => (
|
||||||
<li key={linkProps.navigate}>
|
<li key={linkProps.navigate}>
|
||||||
<Button {...linkProps} className="navigation-link" activeClass="navigation-link--active" />
|
<Button {...linkProps} className="navigation-link" activeClass="navigation-link--active" />
|
||||||
|
@ -120,11 +125,6 @@ function SideNavigation(props: Props) {
|
||||||
|
|
||||||
{expanded &&
|
{expanded &&
|
||||||
[
|
[
|
||||||
// @if TARGET='app'
|
|
||||||
{
|
|
||||||
...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY),
|
|
||||||
},
|
|
||||||
// @endif
|
|
||||||
{
|
{
|
||||||
...buildLink(PAGES.CHANNELS, __('Channels'), ICONS.CHANNEL),
|
...buildLink(PAGES.CHANNELS, __('Channels'), ICONS.CHANNEL),
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,7 @@ 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 EmbedArea from 'component/embedArea';
|
import EmbedTextArea from 'component/embedTextArea';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
claim: Claim,
|
claim: Claim,
|
||||||
|
@ -58,7 +58,7 @@ 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 && !isChannel && <EmbedArea label={__('Embedded')} claim={claim} noSnackbar />}
|
{webShareable && !isChannel && <EmbedTextArea label={__('Embedded')} claim={claim} noSnackbar />}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,8 @@ class TransactionListItem extends React.PureComponent<Props> {
|
||||||
if (type === TXN_TYPES.TIP) {
|
if (type === TXN_TYPES.TIP) {
|
||||||
return <Button button="secondary" icon={ICONS.UNLOCK} onClick={this.abandonClaim} title={__('Unlock Tip')} />;
|
return <Button button="secondary" icon={ICONS.UNLOCK} onClick={this.abandonClaim} title={__('Unlock Tip')} />;
|
||||||
}
|
}
|
||||||
return <Button button="secondary" icon={ICONS.DELETE} onClick={this.abandonClaim} title={__('Abandon Claim')} />;
|
const abandonTitle = type === TXN_TYPES.SUPPORT ? 'Abandon Support' : 'Abandon Claim';
|
||||||
|
return <Button button="secondary" icon={ICONS.DELETE} onClick={this.abandonClaim} title={__(abandonTitle)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
abandonClaim() {
|
abandonClaim() {
|
||||||
|
|
|
@ -38,7 +38,6 @@ type Props = {
|
||||||
changeVolume: number => void,
|
changeVolume: number => void,
|
||||||
savePosition: (string, number) => void,
|
savePosition: (string, number) => void,
|
||||||
changeMute: boolean => void,
|
changeMute: boolean => void,
|
||||||
setPlayingUri: (string | null) => void,
|
|
||||||
source: string,
|
source: string,
|
||||||
contentType: string,
|
contentType: string,
|
||||||
thumbnail: string,
|
thumbnail: string,
|
||||||
|
@ -48,18 +47,7 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function VideoViewer(props: Props) {
|
function VideoViewer(props: Props) {
|
||||||
const {
|
const { contentType, source, onEndedCB, changeVolume, changeMute, volume, muted, thumbnail, claim } = props;
|
||||||
contentType,
|
|
||||||
source,
|
|
||||||
setPlayingUri,
|
|
||||||
onEndedCB,
|
|
||||||
changeVolume,
|
|
||||||
changeMute,
|
|
||||||
volume,
|
|
||||||
muted,
|
|
||||||
thumbnail,
|
|
||||||
claim,
|
|
||||||
} = props;
|
|
||||||
const claimId = claim && claim.claim_id;
|
const claimId = claim && claim.claim_id;
|
||||||
const videoRef = useRef();
|
const videoRef = useRef();
|
||||||
const isAudio = contentType.includes('audio');
|
const isAudio = contentType.includes('audio');
|
||||||
|
@ -85,13 +73,13 @@ function VideoViewer(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function doEnded() {
|
function doEnded() {
|
||||||
// clear position
|
|
||||||
setPlayingUri(null);
|
|
||||||
onEndedCB();
|
onEndedCB();
|
||||||
}
|
}
|
||||||
|
|
||||||
function doPause(e: Event) {
|
function doPause(e: Event) {
|
||||||
// store position e.target.currentTime
|
// store position e.target.currentTime
|
||||||
}
|
}
|
||||||
|
|
||||||
function doVolume(e: Event) {
|
function doVolume(e: Event) {
|
||||||
// $FlowFixMe volume is missing in EventTarget
|
// $FlowFixMe volume is missing in EventTarget
|
||||||
changeVolume(e.target.volume);
|
changeVolume(e.target.volume);
|
||||||
|
|
|
@ -27,3 +27,4 @@ exports.CHANNELS_FOLLOWING_MANAGE = 'following/channels/manage';
|
||||||
exports.WALLET = 'wallet';
|
exports.WALLET = 'wallet';
|
||||||
exports.BLOCKED = 'blocked';
|
exports.BLOCKED = 'blocked';
|
||||||
exports.CHANNELS = 'channels';
|
exports.CHANNELS = 'channels';
|
||||||
|
exports.EMBED = 'embed';
|
||||||
|
|
|
@ -3,7 +3,13 @@ import { useState, useEffect } from 'react';
|
||||||
export default function usePersistedState(key, firstTimeDefault) {
|
export default function usePersistedState(key, firstTimeDefault) {
|
||||||
// If no key is passed in, act as a normal `useState`
|
// If no key is passed in, act as a normal `useState`
|
||||||
let defaultValue;
|
let defaultValue;
|
||||||
if (key) {
|
let localStorageAvailable;
|
||||||
|
try {
|
||||||
|
localStorageAvailable = Boolean(window.localStorage);
|
||||||
|
} catch (e) {
|
||||||
|
localStorageAvailable = false;
|
||||||
|
}
|
||||||
|
if (key && localStorageAvailable) {
|
||||||
let item = localStorage.getItem(key);
|
let item = localStorage.getItem(key);
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
|
@ -27,7 +33,7 @@ export default function usePersistedState(key, firstTimeDefault) {
|
||||||
const [value, setValue] = useState(defaultValue);
|
const [value, setValue] = useState(defaultValue);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (key) {
|
if (key && localStorageAvailable) {
|
||||||
localStorage.setItem(key, typeof value === 'object' ? JSON.stringify(value) : value);
|
localStorage.setItem(key, typeof value === 'object' ? JSON.stringify(value) : value);
|
||||||
}
|
}
|
||||||
}, [key, value]);
|
}, [key, value]);
|
||||||
|
|
10
ui/i18n.js
10
ui/i18n.js
|
@ -4,6 +4,12 @@ let fs = require('fs');
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
let knownMessages = null;
|
let knownMessages = null;
|
||||||
|
let localStorageAvailable;
|
||||||
|
try {
|
||||||
|
localStorageAvailable = Boolean(window.localStorage);
|
||||||
|
} catch (e) {
|
||||||
|
localStorageAvailable = false;
|
||||||
|
}
|
||||||
|
|
||||||
window.i18n_messages = window.i18n_messages || {};
|
window.i18n_messages = window.i18n_messages || {};
|
||||||
|
|
||||||
|
@ -44,7 +50,9 @@ function saveMessage(message) {
|
||||||
// @endif
|
// @endif
|
||||||
|
|
||||||
export function __(message, tokens) {
|
export function __(message, tokens) {
|
||||||
const language = window.localStorage.getItem('language') || 'en';
|
const language = localStorageAvailable
|
||||||
|
? window.localStorage.getItem('language') || 'en'
|
||||||
|
: window.navigator.language.slice(0, 2) || 'en';
|
||||||
|
|
||||||
if (!isProduction) {
|
if (!isProduction) {
|
||||||
saveMessage(message);
|
saveMessage(message);
|
||||||
|
|
23
ui/page/embedWrapper/index.js
Normal file
23
ui/page/embedWrapper/index.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import EmbedWrapperPage from './view';
|
||||||
|
import { doResolveUri, makeSelectClaimForUri, buildURI } from 'lbry-redux';
|
||||||
|
|
||||||
|
const select = (state, props) => {
|
||||||
|
const { match } = props;
|
||||||
|
const { params } = match;
|
||||||
|
const { claimName, claimId } = params;
|
||||||
|
const uri = claimName && claimId ? buildURI({ claimName, claimId }) : '';
|
||||||
|
return {
|
||||||
|
uri,
|
||||||
|
claim: makeSelectClaimForUri(uri)(state),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(EmbedWrapperPage);
|
30
ui/page/embedWrapper/view.jsx
Normal file
30
ui/page/embedWrapper/view.jsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// @flow
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import FileRender from 'component/fileRender';
|
||||||
|
import Spinner from 'component/spinner';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
uri: string,
|
||||||
|
resolveUri: string => void,
|
||||||
|
claim: Claim,
|
||||||
|
};
|
||||||
|
const EmbedWrapperPage = (props: Props) => {
|
||||||
|
const { resolveUri, claim, uri } = props;
|
||||||
|
useEffect(() => {
|
||||||
|
if (resolveUri && uri) {
|
||||||
|
resolveUri(uri);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (uri && claim) {
|
||||||
|
return (
|
||||||
|
<div className={'embed__wrapper'}>
|
||||||
|
<FileRender uri={uri} embedded />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return <Spinner />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmbedWrapperPage;
|
|
@ -45,12 +45,14 @@ const defaultState: AppState = {
|
||||||
modal: null,
|
modal: null,
|
||||||
modalProps: {},
|
modalProps: {},
|
||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
upgradeSkipped: sessionStorage.getItem('upgradeSkipped') === 'true',
|
|
||||||
daemonVersionMatched: null,
|
daemonVersionMatched: null,
|
||||||
daemonReady: false,
|
daemonReady: false,
|
||||||
hasSignature: false,
|
hasSignature: false,
|
||||||
badgeNumber: 0,
|
badgeNumber: 0,
|
||||||
volume: Number(sessionStorage.getItem('volume')) || 1,
|
volume: 1,
|
||||||
|
// @if TARGET='app'
|
||||||
|
upgradeSkipped: sessionStorage.getItem('upgradeSkipped') === 'true',
|
||||||
|
// @endif
|
||||||
muted: false,
|
muted: false,
|
||||||
autoUpdateDownloaded: false,
|
autoUpdateDownloaded: false,
|
||||||
autoUpdateDeclined: false,
|
autoUpdateDeclined: false,
|
||||||
|
@ -71,6 +73,10 @@ const defaultState: AppState = {
|
||||||
|
|
||||||
// @@router comes from react-router
|
// @@router comes from react-router
|
||||||
// This action is dispatched any time a user navigates forward or back
|
// This action is dispatched any time a user navigates forward or back
|
||||||
|
try {
|
||||||
|
defaultState.volume = Number(sessionStorage.getItem('volume'));
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
reducers['@@router/LOCATION_CHANGE'] = (state, action) => {
|
reducers['@@router/LOCATION_CHANGE'] = (state, action) => {
|
||||||
const { currentScroll } = state;
|
const { currentScroll } = state;
|
||||||
const scrollHistory = (state.scrollHistory && state.scrollHistory.slice()) || [];
|
const scrollHistory = (state.scrollHistory && state.scrollHistory.slice()) || [];
|
||||||
|
|
|
@ -5,6 +5,14 @@ import SUPPORTED_LANGUAGES from 'constants/supported_languages';
|
||||||
import { ACTIONS as LBRY_REDUX_ACTIONS, SHARED_PREFERENCES } from 'lbry-redux';
|
import { ACTIONS as LBRY_REDUX_ACTIONS, SHARED_PREFERENCES } from 'lbry-redux';
|
||||||
|
|
||||||
const reducers = {};
|
const reducers = {};
|
||||||
|
let settingLanguage = [];
|
||||||
|
try {
|
||||||
|
let appLanguage = window.localStorage.getItem(SETTINGS.LANGUAGE);
|
||||||
|
settingLanguage.push(appLanguage);
|
||||||
|
} catch (e) {}
|
||||||
|
settingLanguage.push(window.navigator.language.slice(0, 2));
|
||||||
|
settingLanguage.push('en');
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
isNight: false,
|
isNight: false,
|
||||||
loadedLanguages: [...Object.keys(window.i18n_messages), 'en'] || ['en'],
|
loadedLanguages: [...Object.keys(window.i18n_messages), 'en'] || ['en'],
|
||||||
|
@ -21,11 +29,7 @@ const defaultState = {
|
||||||
[SETTINGS.ENABLE_SYNC]: true,
|
[SETTINGS.ENABLE_SYNC]: true,
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
[SETTINGS.LANGUAGE]: [
|
[SETTINGS.LANGUAGE]: settingLanguage.find(language => SUPPORTED_LANGUAGES[language]),
|
||||||
window.localStorage.getItem(SETTINGS.LANGUAGE),
|
|
||||||
window.navigator.language.slice(0, 2),
|
|
||||||
'en',
|
|
||||||
].find(language => SUPPORTED_LANGUAGES[language]),
|
|
||||||
[SETTINGS.THEME]: __('light'),
|
[SETTINGS.THEME]: __('light'),
|
||||||
[SETTINGS.THEMES]: [__('light'), __('dark')],
|
[SETTINGS.THEMES]: [__('light'), __('dark')],
|
||||||
[SETTINGS.SUPPORT_OPTION]: false,
|
[SETTINGS.SUPPORT_OPTION]: false,
|
||||||
|
@ -111,7 +115,7 @@ reducers[LBRY_REDUX_ACTIONS.SHARED_PREFERENCE_SET] = (state, action) => {
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
sharedPreferences,
|
sharedPreferences,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
reducers[ACTIONS.CLIENT_SETTING_CHANGED] = (state, action) => {
|
reducers[ACTIONS.CLIENT_SETTING_CHANGED] = (state, action) => {
|
||||||
const { key, value } = action.data;
|
const { key, value } = action.data;
|
||||||
|
@ -124,10 +128,7 @@ reducers[ACTIONS.CLIENT_SETTING_CHANGED] = (state, action) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
reducers[LBRY_REDUX_ACTIONS.USER_STATE_POPULATE] = (
|
reducers[LBRY_REDUX_ACTIONS.USER_STATE_POPULATE] = (state, action) => {
|
||||||
state,
|
|
||||||
action,
|
|
||||||
) => {
|
|
||||||
const { settings: sharedPreferences } = action.data;
|
const { settings: sharedPreferences } = action.data;
|
||||||
|
|
||||||
// process clientSettings and daemonSettings
|
// process clientSettings and daemonSettings
|
||||||
|
@ -135,8 +136,8 @@ reducers[LBRY_REDUX_ACTIONS.USER_STATE_POPULATE] = (
|
||||||
};
|
};
|
||||||
|
|
||||||
reducers[LBRY_REDUX_ACTIONS.SAVE_CUSTOM_WALLET_SERVERS] = (state, action) => {
|
reducers[LBRY_REDUX_ACTIONS.SAVE_CUSTOM_WALLET_SERVERS] = (state, action) => {
|
||||||
return Object.assign({}, state, {customWalletServers: action.data});
|
return Object.assign({}, state, { customWalletServers: action.data });
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function reducer(state = defaultState, action) {
|
export default function reducer(state = defaultState, action) {
|
||||||
const handler = reducers[action.type];
|
const handler = reducers[action.type];
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
@import 'component/comments';
|
@import 'component/comments';
|
||||||
@import 'component/content';
|
@import 'component/content';
|
||||||
@import 'component/dat-gui';
|
@import 'component/dat-gui';
|
||||||
|
@import 'component/embed-player';
|
||||||
@import 'component/expandable';
|
@import 'component/expandable';
|
||||||
@import 'component/file-properties';
|
@import 'component/file-properties';
|
||||||
@import 'component/file-render';
|
@import 'component/file-render';
|
||||||
|
|
|
@ -302,6 +302,12 @@
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-medium) {
|
||||||
|
.channel-thumbnail {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: $breakpoint-small) {
|
@media (max-width: $breakpoint-small) {
|
||||||
width: calc((100% - var(--spacing-medium) * 1) / 2);
|
width: calc((100% - var(--spacing-medium) * 1) / 2);
|
||||||
margin-bottom: var(--spacing-large);
|
margin-bottom: var(--spacing-large);
|
||||||
|
|
9
ui/scss/component/_embed-player.scss
Normal file
9
ui/scss/component/_embed-player.scss
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
.embed__wrapper {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
|
@ -153,3 +153,92 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-render__embed {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
|
||||||
|
.video-js {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.vjs-big-play-button {
|
||||||
|
@extend .button--icon;
|
||||||
|
@extend .button--play;
|
||||||
|
border: none;
|
||||||
|
position: static;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
.vjs-icon-placeholder {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-overlay__wrapper {
|
||||||
|
position: absolute;
|
||||||
|
left: auto;
|
||||||
|
right: auto;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.9);
|
||||||
|
z-index: 999;
|
||||||
|
color: var(--color-white);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--spacing-large);
|
||||||
|
|
||||||
|
.button--uri-indicator {
|
||||||
|
color: var(--color-gray-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: var(--spacing-small);
|
||||||
|
|
||||||
|
.button,
|
||||||
|
.video-overlay__subtitle,
|
||||||
|
.video-overlay__actions {
|
||||||
|
font-size: var(--font-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-overlay__title {
|
||||||
|
font-size: var(--font-title);
|
||||||
|
font-weight: var(--font-weight-light);
|
||||||
|
margin-top: var(--spacing-medium);
|
||||||
|
margin-bottom: var(--spacing-small);
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
margin: 0;
|
||||||
|
font-size: var(--font-medium);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-overlay__subtitle {
|
||||||
|
color: var(--color-gray-3);
|
||||||
|
margin: var(--spacing-medium) 0;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-overlay__actions {
|
||||||
|
margin-top: var(--spacing-large);
|
||||||
|
|
||||||
|
.button--link {
|
||||||
|
color: var(--color-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
margin-top: var(--spacing-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
stroke: var(--color-input-placeholder);
|
stroke: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,13 @@
|
||||||
padding-right: var(--spacing-small);
|
padding-right: var(--spacing-small);
|
||||||
padding-left: 2.5rem;
|
padding-left: 2.5rem;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
background-color: #677e87;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: white;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
|
|
|
@ -92,6 +92,15 @@
|
||||||
margin-top: var(--spacing-medium);
|
margin-top: var(--spacing-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section__actions--centered {
|
||||||
|
@extend .section__actions;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
@media (max-width: $breakpoint-small) {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: $breakpoint-small) {
|
@media (max-width: $breakpoint-small) {
|
||||||
.section__actions {
|
.section__actions {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
|
@ -4,6 +4,7 @@ $spacing-width: 36px;
|
||||||
|
|
||||||
$breakpoint-xsmall: 600px;
|
$breakpoint-xsmall: 600px;
|
||||||
$breakpoint-small: 900px;
|
$breakpoint-small: 900px;
|
||||||
|
$breakpoint-medium: 1150px;
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
// Width & spacing
|
// Width & spacing
|
||||||
|
|
Loading…
Add table
Reference in a new issue