new text viewer layout
This commit is contained in:
parent
9a6f2a1975
commit
72b9f3efdd
47 changed files with 658 additions and 274 deletions
|
@ -10,7 +10,7 @@ import ReactModal from 'react-modal';
|
|||
import { openContextMenu } from 'util/context-menu';
|
||||
import useKonamiListener from 'util/enhanced-layout';
|
||||
import Yrbl from 'component/yrbl';
|
||||
import FileViewer from 'component/fileViewer';
|
||||
import FloatingViewer from 'component/floatingViewer';
|
||||
import { withRouter } from 'react-router';
|
||||
import usePrevious from 'effects/use-previous';
|
||||
import Nag from 'component/common/nag';
|
||||
|
@ -203,7 +203,7 @@ function App(props: Props) {
|
|||
>
|
||||
<Router />
|
||||
<ModalRouter />
|
||||
<FileViewer pageUri={uri} />
|
||||
<FloatingViewer pageUri={uri} />
|
||||
|
||||
{/* @if TARGET='web' */}
|
||||
<YoutubeWelcome />
|
||||
|
|
|
@ -147,6 +147,7 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
}}
|
||||
className={combinedClassName}
|
||||
activeClassName={activeClass}
|
||||
{...otherProps}
|
||||
>
|
||||
{content}
|
||||
</NavLink>
|
||||
|
|
|
@ -101,7 +101,7 @@ export default function ClaimList(props: Props) {
|
|||
{header !== false && (
|
||||
<React.Fragment>
|
||||
{headerLabel && <label className="claim-list__header-label">{headerLabel}</label>}
|
||||
<div className={classnames('claim-list__header', { 'claim-list__header--small': type === 'small' })}>
|
||||
<div className={classnames('claim-list__header', { 'section__title--small': type === 'small' })}>
|
||||
{header}
|
||||
{loading && <Spinner type="small" />}
|
||||
<div className="claim-list__alt-controls">
|
||||
|
|
|
@ -7,7 +7,7 @@ import { withRouter } from 'react-router-dom';
|
|||
import { openCopyLinkMenu } from 'util/context-menu';
|
||||
import { formatLbryUrlForWeb } from 'util/url';
|
||||
import { isEmpty } from 'util/object';
|
||||
import CardMedia from 'component/cardMedia';
|
||||
import FileThumbnail from 'component/fileThumbnail';
|
||||
import UriIndicator from 'component/uriIndicator';
|
||||
import TruncatedText from 'component/common/truncated-text';
|
||||
import DateTime from 'component/dateTime';
|
||||
|
@ -200,7 +200,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
<ChannelThumbnail uri={uri} obscure={channelIsBlocked} />
|
||||
</UriIndicator>
|
||||
) : (
|
||||
<CardMedia thumbnail={thumbnail} />
|
||||
<FileThumbnail thumbnail={thumbnail} />
|
||||
)}
|
||||
<div className="claim-preview__text">
|
||||
<div className="claim-preview-metadata">
|
||||
|
|
|
@ -1,21 +1,33 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectFileInfoForUri, makeSelectClaimIsMine } from 'lbry-redux';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import {
|
||||
makeSelectClaimIsMine,
|
||||
makeSelectFileInfoForUri,
|
||||
makeSelectClaimForUri,
|
||||
makeSelectContentTypeForUri,
|
||||
doPrepareEdit,
|
||||
} from 'lbry-redux';
|
||||
import { makeSelectCostInfoForUri } from 'lbryinc';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import FileActions from './view';
|
||||
import fs from 'fs';
|
||||
import FilePage from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||
/* availability check is disabled due to poor performance, TBD if it dies forever or requires daemon fix */
|
||||
costInfo: makeSelectCostInfoForUri(props.uri)(state),
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||
contentType: makeSelectContentTypeForUri(props.uri)(state),
|
||||
costInfo: makeSelectCostInfoForUri(props.uri)(state),
|
||||
supportOption: makeSelectClientSetting(SETTINGS.SUPPORT_OPTION)(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||
prepareEdit: (publishData, uri, fileInfo) => dispatch(doPrepareEdit(publishData, uri, fileInfo, fs)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(FileActions);
|
||||
)(FilePage);
|
||||
|
|
|
@ -3,40 +3,114 @@ import * as MODALS from 'constants/modal_types';
|
|||
import * as ICONS from 'constants/icons';
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import Tooltip from 'component/common/tooltip';
|
||||
import FileDownloadLink from 'component/fileDownloadLink';
|
||||
import { buildURI } from 'lbry-redux';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
claimId: string,
|
||||
openModal: (id: string, { uri: string }) => void,
|
||||
claim: StreamClaim,
|
||||
openModal: (id: string, { uri: string, claimIsMine?: boolean, isSupport?: boolean }) => void,
|
||||
prepareEdit: ({}, string, {}) => void,
|
||||
claimIsMine: boolean,
|
||||
fileInfo: FileListItem,
|
||||
costInfo: ?{ cost: number },
|
||||
contentType: string,
|
||||
supportOption: boolean,
|
||||
};
|
||||
|
||||
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 > 0));
|
||||
return (
|
||||
<React.Fragment>
|
||||
function FileActions(props: Props) {
|
||||
const { fileInfo, uri, openModal, claimIsMine, claim, costInfo, contentType, supportOption, prepareEdit } = props;
|
||||
const webShareable =
|
||||
costInfo && costInfo.cost === 0 && contentType && ['video', 'image', 'audio'].includes(contentType.split('/')[0]);
|
||||
const showDelete = claimIsMine || (fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed > 0));
|
||||
const claimId = claim && claim.claim_id;
|
||||
const { signing_channel: signingChannel } = claim;
|
||||
const channelName = signingChannel && signingChannel.name;
|
||||
// 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
|
||||
// We will select the claim id before they publish
|
||||
let editUri;
|
||||
if (claimIsMine) {
|
||||
const uriObject: { streamName: string, streamClaimId: string, channelName?: string } = {
|
||||
streamName: claim.name,
|
||||
streamClaimId: claim.claim_id,
|
||||
};
|
||||
if (channelName) {
|
||||
uriObject.channelName = channelName;
|
||||
}
|
||||
|
||||
editUri = buildURI(uriObject);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="media__actions">
|
||||
<div className="section__actions">
|
||||
<Button
|
||||
button="alt"
|
||||
icon={ICONS.SHARE}
|
||||
label={__('Share')}
|
||||
onClick={() => openModal(MODALS.SOCIAL_SHARE, { uri, webShareable })}
|
||||
/>
|
||||
|
||||
{!claimIsMine && (
|
||||
<Button
|
||||
button="alt"
|
||||
icon={ICONS.TIP}
|
||||
label={__('Tip')}
|
||||
requiresAuth={IS_WEB}
|
||||
title={__('Send a tip to this creator')}
|
||||
onClick={() => openModal(MODALS.SEND_TIP, { uri, claimIsMine, isSupport: false })}
|
||||
/>
|
||||
)}
|
||||
{(claimIsMine || (!claimIsMine && supportOption)) && (
|
||||
<Button
|
||||
button="alt"
|
||||
icon={ICONS.SUPPORT}
|
||||
label={__('Support')}
|
||||
requiresAuth={IS_WEB}
|
||||
title={__('Support this claim')}
|
||||
onClick={() => openModal(MODALS.SEND_TIP, { uri, claimIsMine, isSupport: true })}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="section__actions">
|
||||
{/* @if TARGET='app' */}
|
||||
<FileDownloadLink uri={uri} />
|
||||
{/* @endif */}
|
||||
|
||||
{claimIsMine && (
|
||||
<Button
|
||||
button="alt"
|
||||
icon={ICONS.EDIT}
|
||||
label={__('Edit')}
|
||||
navigate="/$/publish"
|
||||
onClick={() => {
|
||||
prepareEdit(claim, editUri, fileInfo);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showDelete && (
|
||||
<Tooltip label={__('Remove from your library')}>
|
||||
<Button
|
||||
button="alt"
|
||||
icon={ICONS.DELETE}
|
||||
description={__('Delete')}
|
||||
onClick={() => openModal(MODALS.CONFIRM_FILE_REMOVE, { uri })}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Button
|
||||
title={__('Remove from your library')}
|
||||
button="alt"
|
||||
icon={ICONS.DELETE}
|
||||
description={__('Delete')}
|
||||
onClick={() => openModal(MODALS.CONFIRM_FILE_REMOVE, { uri })}
|
||||
/>
|
||||
)}
|
||||
{!claimIsMine && (
|
||||
<Tooltip label={__('Report content')}>
|
||||
<Button button="alt" icon={ICONS.REPORT} href={`https://lbry.com/dmca/${claimId}`} />
|
||||
</Tooltip>
|
||||
<Button
|
||||
title={__('Report content')}
|
||||
button="alt"
|
||||
icon={ICONS.REPORT}
|
||||
href={`https://lbry.com/dmca/${claimId}`}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FileActions;
|
||||
|
|
9
ui/component/fileAuthor/index.js
Normal file
9
ui/component/fileAuthor/index.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectChannelForClaimUri } from 'lbry-redux';
|
||||
import FileAuthor from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
channelUri: makeSelectChannelForClaimUri(props.uri)(state),
|
||||
});
|
||||
|
||||
export default connect(select)(FileAuthor);
|
19
ui/component/fileAuthor/view.jsx
Normal file
19
ui/component/fileAuthor/view.jsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import ClaimPreview from 'component/claimPreview';
|
||||
|
||||
type Props = {
|
||||
channelUri: string,
|
||||
};
|
||||
|
||||
function LayoutWrapperDocument(props: Props) {
|
||||
const { channelUri } = props;
|
||||
|
||||
return channelUri ? (
|
||||
<ClaimPreview uri={channelUri} type="inline" properties={false} hideBlock />
|
||||
) : (
|
||||
<div className="claim-preview--inline claim-preview-title">{__('Anonymous')}</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutWrapperDocument;
|
|
@ -43,11 +43,9 @@ class FileDetails extends PureComponent<Props> {
|
|||
<Fragment>
|
||||
<Expandable>
|
||||
{description && (
|
||||
<Fragment>
|
||||
<div className="media__info-text">
|
||||
<MarkdownPreview content={description} />
|
||||
</div>
|
||||
</Fragment>
|
||||
<div className="media__info-text">
|
||||
<MarkdownPreview content={description} />
|
||||
</div>
|
||||
)}
|
||||
<ClaimTags uri={uri} type="large" />
|
||||
<table className="table table--condensed table--fixed table--file-details">
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// @flow
|
||||
import { remote } from 'electron';
|
||||
import React, { Suspense, Fragment } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
import VideoViewer from 'component/viewers/videoViewer';
|
||||
import ImageViewer from 'component/viewers/imageViewer';
|
||||
|
@ -186,8 +187,10 @@ class FileRender extends React.PureComponent<Props> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { mediaType } = this.props;
|
||||
|
||||
return (
|
||||
<div className="file-render">
|
||||
<div className={classnames('file-render', { 'file-render--document': mediaType === 'text' })}>
|
||||
<Suspense fallback={<div />}>{this.renderViewer()}</Suspense>
|
||||
</div>
|
||||
);
|
||||
|
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
@ -21,11 +21,11 @@ class CardMedia extends React.PureComponent<Props> {
|
|||
// @if TARGET='web'
|
||||
// Pass image urls through a compression proxy
|
||||
url = thumbnail || Placeholder;
|
||||
// url = thumbnail
|
||||
// ? 'https://ext.thumbnails.lbry.com/400x,q55/' +
|
||||
// The image server will redirect if we don't remove the double slashes after http(s)
|
||||
// thumbnail.replace('https://', 'https:/').replace('http://', 'http:/')
|
||||
// : Placeholder;
|
||||
// url = thumbnail
|
||||
// ? 'https://ext.thumbnails.lbry.com/400x,q55/' +
|
||||
// The image server will redirect if we don't remove the double slashes after http(s)
|
||||
// thumbnail.replace('https://', 'https:/').replace('http://', 'http:/')
|
||||
// : Placeholder;
|
||||
// @endif
|
||||
// @if TARGET='app'
|
||||
url = thumbnail || Placeholder;
|
9
ui/component/fileViewCount/index.js
Normal file
9
ui/component/fileViewCount/index.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectViewCountForUri } from 'lbryinc';
|
||||
import FileViewCount from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
viewCount: makeSelectViewCountForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
export default connect(select)(FileViewCount);
|
20
ui/component/fileViewCount/view.jsx
Normal file
20
ui/component/fileViewCount/view.jsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import HelpLink from 'component/common/help-link';
|
||||
|
||||
type Props = {
|
||||
viewCount: string,
|
||||
};
|
||||
|
||||
function LayoutWrapperDocument(props: Props) {
|
||||
const { viewCount } = props;
|
||||
|
||||
return (
|
||||
<span>
|
||||
{viewCount !== 1 ? __('%view_count% Views', { view_count: viewCount }) : __('1 View')}
|
||||
<HelpLink href="https://lbry.com/faq/views" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutWrapperDocument;
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
// This component is entirely for triggering the start of a file view
|
||||
// The actual viewer for a file exists in FileViewer
|
||||
// The actual viewer for a file exists in TextViewer and FloatingViewer
|
||||
// They can't exist in one component because we need to handle/listen for the start of a new file view
|
||||
// while a file is currently being viewed
|
||||
import React, { useEffect, useCallback, Fragment } from 'react';
|
||||
|
@ -27,9 +27,10 @@ type Props = {
|
|||
hasCostInfo: boolean,
|
||||
costInfo: any,
|
||||
isAutoPlayable: boolean,
|
||||
inline: boolean,
|
||||
};
|
||||
|
||||
export default function FileViewer(props: Props) {
|
||||
export default function FileViewerInitiator(props: Props) {
|
||||
const {
|
||||
play,
|
||||
mediaType,
|
||||
|
@ -49,6 +50,7 @@ export default function FileViewer(props: Props) {
|
|||
const cost = costInfo && costInfo.cost;
|
||||
const forceVideo = ['application/x-ext-mkv', 'video/x-matroska'].includes(contentType);
|
||||
const isPlayable = ['audio', 'video'].includes(mediaType) || forceVideo;
|
||||
const isText = mediaType === 'text';
|
||||
const fileStatus = fileInfo && fileInfo.status;
|
||||
const webStreamOnly = contentType === 'application/pdf' || mediaType === 'text';
|
||||
const supported = IS_WEB ? (!cost && isStreamable) || webStreamOnly || forceVideo : true;
|
||||
|
@ -95,10 +97,10 @@ export default function FileViewer(props: Props) {
|
|||
|
||||
useEffect(() => {
|
||||
const videoOnPage = document.querySelector('video');
|
||||
if (autoplay && !videoOnPage && isAutoPlayable && hasCostInfo && cost === 0) {
|
||||
if (((autoplay && !videoOnPage && isAutoPlayable) || isText) && hasCostInfo && cost === 0) {
|
||||
viewFile();
|
||||
}
|
||||
}, [autoplay, viewFile, isAutoPlayable, hasCostInfo, cost]);
|
||||
}, [autoplay, viewFile, isAutoPlayable, hasCostInfo, cost, isText]);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -108,6 +110,7 @@ export default function FileViewer(props: Props) {
|
|||
className={classnames({
|
||||
content__cover: supported,
|
||||
'content__cover--disabled': !supported,
|
||||
'content__cover--hidden-for-text': isText,
|
||||
'card__media--nsfw': obscurePreview,
|
||||
'card__media--disabled': supported && !fileInfo && insufficientCredits,
|
||||
})}
|
||||
|
@ -127,6 +130,7 @@ export default function FileViewer(props: Props) {
|
|||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isPlaying && supported && (
|
||||
<Button
|
||||
onClick={viewFile}
|
||||
|
|
|
@ -8,7 +8,7 @@ import FileRender from 'component/fileRender';
|
|||
import UriIndicator from 'component/uriIndicator';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import usePrevious from 'effects/use-previous';
|
||||
import { FILE_WRAPPER_CLASS } from 'page/file/view';
|
||||
import { FILE_WRAPPER_CLASS } from 'component/layoutWrapperFile/view';
|
||||
import Draggable from 'react-draggable';
|
||||
import Tooltip from 'component/common/tooltip';
|
||||
import { onFullscreenChange } from 'util/full-screen';
|
||||
|
@ -96,7 +96,6 @@ export default function FileViewer(props: Props) {
|
|||
function handleResize() {
|
||||
const element = document.querySelector(`.${FILE_WRAPPER_CLASS}`);
|
||||
if (!element) {
|
||||
console.error("Can't find file viewer wrapper to attach to the inline viewer to"); // eslint-disable-line
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -125,7 +124,10 @@ export default function FileViewer(props: Props) {
|
|||
}
|
||||
|
||||
const hidePlayer =
|
||||
!isPlaying || !uri || (!inline && (isMobile || !floatingPlayerEnabled || !['audio', 'video'].includes(mediaType)));
|
||||
mediaType === 'text' ||
|
||||
!isPlaying ||
|
||||
!uri ||
|
||||
(!inline && (isMobile || !floatingPlayerEnabled || !['audio', 'video'].includes(mediaType)));
|
||||
|
||||
if (hidePlayer) {
|
||||
return null;
|
35
ui/component/layoutWrapperFile/index.js
Normal file
35
ui/component/layoutWrapperFile/index.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { connect } from 'react-redux';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import {
|
||||
makeSelectClaimIsMine,
|
||||
makeSelectFileInfoForUri,
|
||||
makeSelectClaimForUri,
|
||||
makeSelectContentTypeForUri,
|
||||
doPrepareEdit,
|
||||
makeSelectTitleForUri,
|
||||
} from 'lbry-redux';
|
||||
import { makeSelectCostInfoForUri } from 'lbryinc';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import fs from 'fs';
|
||||
import FilePage from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||
contentType: makeSelectContentTypeForUri(props.uri)(state),
|
||||
costInfo: makeSelectCostInfoForUri(props.uri)(state),
|
||||
supportOption: makeSelectClientSetting(SETTINGS.SUPPORT_OPTION)(state),
|
||||
title: makeSelectTitleForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||
prepareEdit: (publishData, uri, fileInfo) => dispatch(doPrepareEdit(publishData, uri, fileInfo, fs)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(FilePage);
|
89
ui/component/layoutWrapperFile/view.jsx
Normal file
89
ui/component/layoutWrapperFile/view.jsx
Normal file
|
@ -0,0 +1,89 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import { normalizeURI } from 'lbry-redux';
|
||||
import FileViewerInitiator from 'component/fileViewerInitiator';
|
||||
import FilePrice from 'component/filePrice';
|
||||
import FileDetails from 'component/fileDetails';
|
||||
import FileAuthor from 'component/fileAuthor';
|
||||
import FileActions from 'component/fileActions';
|
||||
import DateTime from 'component/dateTime';
|
||||
import RecommendedContent from 'component/recommendedContent';
|
||||
import CommentsList from 'component/commentsList';
|
||||
import CommentCreate from 'component/commentCreate';
|
||||
import ClaimUri from 'component/claimUri';
|
||||
import FileViewCount from 'component/fileViewCount';
|
||||
|
||||
export const FILE_WRAPPER_CLASS = 'grid-area--content';
|
||||
|
||||
type Props = {
|
||||
claim: StreamClaim,
|
||||
fileInfo: FileListItem,
|
||||
uri: string,
|
||||
claimIsMine: boolean,
|
||||
costInfo: ?{ cost: number },
|
||||
balance: number,
|
||||
title: string,
|
||||
nsfw: boolean,
|
||||
};
|
||||
|
||||
function LayoutWrapperFile(props: Props) {
|
||||
const { claim, uri, claimIsMine, costInfo, balance, title, nsfw } = props;
|
||||
const insufficientCredits = !claimIsMine && costInfo && costInfo.cost > balance;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ClaimUri uri={uri} />
|
||||
<div className={`card ${FILE_WRAPPER_CLASS}`}>
|
||||
<FileViewerInitiator uri={uri} insufficientCredits={insufficientCredits} />
|
||||
</div>
|
||||
|
||||
<div className="media__title">
|
||||
<span className="media__title-badge">
|
||||
{nsfw && <span className="badge badge--tag-mature">{__('Mature')}</span>}
|
||||
</span>
|
||||
<span className="media__title-badge">
|
||||
<FilePrice badge uri={normalizeURI(uri)} />
|
||||
</span>
|
||||
<h1 className="media__title-text">{title}</h1>
|
||||
</div>
|
||||
|
||||
<div className="columns">
|
||||
<div className="grid-area--info">
|
||||
<div className="media__subtitle--between">
|
||||
<DateTime uri={uri} show={DateTime.SHOW_DATE} />
|
||||
<FileViewCount uri={uri} />
|
||||
</div>
|
||||
|
||||
<FileActions uri={uri} />
|
||||
|
||||
<div className="section__divider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<FileAuthor uri={uri} />
|
||||
|
||||
<div className="section">
|
||||
<FileDetails uri={uri} />
|
||||
</div>
|
||||
|
||||
<div className="section__divider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div className="section__title--small">{__('Comments')}</div>
|
||||
<section className="section">
|
||||
<CommentCreate uri={uri} />
|
||||
</section>
|
||||
<section className="section">
|
||||
<CommentsList uri={uri} />
|
||||
</section>
|
||||
</div>
|
||||
<div className="grid-area--related">
|
||||
<RecommendedContent uri={uri} claimId={claim.claim_id} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutWrapperFile;
|
39
ui/component/layoutWrapperText/index.js
Normal file
39
ui/component/layoutWrapperText/index.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { connect } from 'react-redux';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import {
|
||||
makeSelectClaimIsMine,
|
||||
makeSelectFileInfoForUri,
|
||||
makeSelectClaimForUri,
|
||||
makeSelectContentTypeForUri,
|
||||
doPrepareEdit,
|
||||
makeSelectTitleForUri,
|
||||
makeSelectMetadataForUri,
|
||||
makeSelectThumbnailForUri,
|
||||
} from 'lbry-redux';
|
||||
import { makeSelectCostInfoForUri } from 'lbryinc';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import fs from 'fs';
|
||||
import LayoutWrapperNonDocument from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||
contentType: makeSelectContentTypeForUri(props.uri)(state),
|
||||
costInfo: makeSelectCostInfoForUri(props.uri)(state),
|
||||
supportOption: makeSelectClientSetting(SETTINGS.SUPPORT_OPTION)(state),
|
||||
title: makeSelectTitleForUri(props.uri)(state),
|
||||
metadata: makeSelectMetadataForUri(props.uri)(state),
|
||||
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||
prepareEdit: (publishData, uri, fileInfo) => dispatch(doPrepareEdit(publishData, uri, fileInfo, fs)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(LayoutWrapperNonDocument);
|
98
ui/component/layoutWrapperText/view.jsx
Normal file
98
ui/component/layoutWrapperText/view.jsx
Normal file
|
@ -0,0 +1,98 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import { normalizeURI } from 'lbry-redux';
|
||||
import FilePrice from 'component/filePrice';
|
||||
import FileAuthor from 'component/fileAuthor';
|
||||
import FileThumbnail from 'component/fileThumbnail';
|
||||
import FileViewCount from 'component/fileViewCount';
|
||||
import FileActions from 'component/fileActions';
|
||||
import TextViewer from 'component/textViewer';
|
||||
import DateTime from 'component/dateTime';
|
||||
import RecommendedContent from 'component/recommendedContent';
|
||||
import CommentsList from 'component/commentsList';
|
||||
import CommentCreate from 'component/commentCreate';
|
||||
import MarkdownPreview from 'component/common/markdown-preview';
|
||||
import ClaimUri from 'component/claimUri';
|
||||
import FileViewerInitiator from 'component/fileViewerInitiator';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
metadata: StreamMetadata,
|
||||
title: string,
|
||||
nsfw: boolean,
|
||||
claim: StreamClaim,
|
||||
thumbnail: ?string,
|
||||
};
|
||||
|
||||
function LayoutWrapperDocument(props: Props) {
|
||||
const { uri, claim, metadata, title, nsfw, thumbnail } = props;
|
||||
const { description } = metadata;
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<div className="main__document-wrapper">
|
||||
<ClaimUri uri={uri} />
|
||||
|
||||
<div className="media__title">
|
||||
<span className="media__title-badge">
|
||||
{nsfw && <span className="badge badge--tag-mature">{__('Mature')}</span>}
|
||||
</span>
|
||||
<span className="media__title-badge">
|
||||
<FilePrice badge uri={normalizeURI(uri)} />
|
||||
</span>
|
||||
<h1 className="media__title-text">{title}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="media__document-thumbnail">
|
||||
<FileThumbnail thumbnail={thumbnail} />
|
||||
</div>
|
||||
|
||||
<div className="section main__document-wrapper">
|
||||
<div className="section__subtitle">
|
||||
<em>
|
||||
<MarkdownPreview content={description} />
|
||||
</em>
|
||||
</div>
|
||||
<div className="media__subtitle--between">
|
||||
<DateTime uri={uri} show={DateTime.SHOW_DATE} />
|
||||
<FileViewCount uri={uri} />
|
||||
</div>
|
||||
<FileActions uri={uri} />
|
||||
|
||||
<div className="section__divider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<FileAuthor uri={uri} />
|
||||
|
||||
<div className="section__divider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
{/* Render the initiator to trigger the view of the file */}
|
||||
<FileViewerInitiator uri={uri} />
|
||||
<TextViewer uri={uri} />
|
||||
|
||||
<div className="section__divider">
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="columns">
|
||||
<div>
|
||||
<div className="section__title--small">{__('Comments')}</div>
|
||||
<section className="section">
|
||||
<CommentCreate uri={uri} />
|
||||
</section>
|
||||
<section className="section">
|
||||
<CommentsList uri={uri} />
|
||||
</section>
|
||||
</div>
|
||||
<RecommendedContent uri={uri} claimId={claim.claim_id} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutWrapperDocument;
|
34
ui/component/textViewer/index.js
Normal file
34
ui/component/textViewer/index.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
makeSelectFileInfoForUri,
|
||||
makeSelectStreamingUrlForUri,
|
||||
makeSelectMediaTypeForUri,
|
||||
makeSelectContentTypeForUri,
|
||||
makeSelectUriIsStreamable,
|
||||
} from 'lbry-redux';
|
||||
import { doClaimEligiblePurchaseRewards } from 'lbryinc';
|
||||
import { makeSelectIsPlaying } from 'redux/selectors/content';
|
||||
import { withRouter } from 'react-router';
|
||||
import { doAnalyticsView } from 'redux/actions/app';
|
||||
import FileViewer from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
mediaType: makeSelectMediaTypeForUri(props.uri)(state),
|
||||
contentType: makeSelectContentTypeForUri(props.uri)(state),
|
||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||
isPlaying: makeSelectIsPlaying(props.uri)(state),
|
||||
streamingUrl: makeSelectStreamingUrlForUri(props.uri)(state),
|
||||
isStreamable: makeSelectUriIsStreamable(props.uri)(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
triggerAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
|
||||
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
|
||||
});
|
||||
|
||||
export default withRouter(
|
||||
connect(
|
||||
select,
|
||||
perform
|
||||
)(FileViewer)
|
||||
);
|
60
ui/component/textViewer/view.jsx
Normal file
60
ui/component/textViewer/view.jsx
Normal file
|
@ -0,0 +1,60 @@
|
|||
// @flow
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import FileRender from 'component/fileRender';
|
||||
import usePrevious from 'effects/use-previous';
|
||||
|
||||
type Props = {
|
||||
mediaType: string,
|
||||
contentType: string,
|
||||
isPlaying: boolean,
|
||||
fileInfo: FileListItem,
|
||||
uri: string,
|
||||
isStreamable: boolean,
|
||||
streamingUrl?: string,
|
||||
triggerAnalyticsView: (string, number) => Promise<any>,
|
||||
claimRewards: () => void,
|
||||
};
|
||||
|
||||
export default function TextViewer(props: Props) {
|
||||
const {
|
||||
isPlaying,
|
||||
fileInfo,
|
||||
uri,
|
||||
streamingUrl,
|
||||
isStreamable,
|
||||
triggerAnalyticsView,
|
||||
claimRewards,
|
||||
mediaType,
|
||||
contentType,
|
||||
} = props;
|
||||
const [playTime, setPlayTime] = useState();
|
||||
const webStreamOnly = contentType === 'application/pdf' || mediaType === 'text';
|
||||
const previousUri = usePrevious(uri);
|
||||
const isNewView = uri && previousUri !== uri && isPlaying;
|
||||
const [hasRecordedView, setHasRecordedView] = useState(false);
|
||||
const isReadyToPlay = (IS_WEB && (isStreamable || streamingUrl || webStreamOnly)) || (fileInfo && fileInfo.completed);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNewView) {
|
||||
setPlayTime(Date.now());
|
||||
}
|
||||
}, [isNewView, uri]);
|
||||
|
||||
useEffect(() => {
|
||||
if (playTime && isReadyToPlay && !hasRecordedView) {
|
||||
const timeToStart = Date.now() - playTime;
|
||||
triggerAnalyticsView(uri, timeToStart).then(() => {
|
||||
claimRewards();
|
||||
setHasRecordedView(false);
|
||||
setPlayTime(null);
|
||||
});
|
||||
}
|
||||
}, [setPlayTime, triggerAnalyticsView, isReadyToPlay, hasRecordedView, playTime, uri, claimRewards]);
|
||||
|
||||
return (
|
||||
<div className={classnames('content__viewersss')}>
|
||||
{isReadyToPlay ? <FileRender uri={uri} /> : <div className="placeholder--text-document" />}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -98,12 +98,11 @@ class DocumentViewer extends React.PureComponent<Props, State> {
|
|||
render() {
|
||||
const { error, loading, content } = this.state;
|
||||
const isReady = content && !error;
|
||||
const loadingMessage = __('Rendering document.');
|
||||
const errorMessage = __("Sorry, looks like we can't load the document.");
|
||||
|
||||
return (
|
||||
<div className="file-render__viewer--document">
|
||||
{loading && !error && <LoadingScreen status={loadingMessage} spinner />}
|
||||
{loading && !error && <div className="placeholder--text-document" />}
|
||||
{error && <LoadingScreen status={errorMessage} spinner={!error} />}
|
||||
{isReady && this.renderDocument()}
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import CardMedia from 'component/cardMedia';
|
||||
import FileThumbnail from 'component/fileThumbnail';
|
||||
type Props = {
|
||||
params: UpdatePublishFormData,
|
||||
progress: string,
|
||||
|
@ -13,7 +13,7 @@ export default function WebUploadItem(props: Props) {
|
|||
|
||||
return (
|
||||
<li className={'claim-preview claim-preview--inactive card--inline'}>
|
||||
<CardMedia thumbnail={params.thumbnail_url} />
|
||||
<FileThumbnail thumbnail={params.thumbnail_url} />
|
||||
<div className={'claim-preview-metadata'}>
|
||||
<div className="claim-preview-info">
|
||||
<div className="claim-preview-title">{params.title}</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import WalletAddress from './node_modules/component/walletAddress';
|
||||
import Page from './node_modules/component/page';
|
||||
import WalletAddress from 'component/walletAddress';
|
||||
import Page from 'component/page';
|
||||
|
||||
const WalletAddressPage = () => (
|
||||
<Page className="main--contained">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import WalletSend from './node_modules/component/walletSend';
|
||||
import WalletSend from 'component/walletSend';
|
||||
|
||||
const WalletSendModal = () => (
|
||||
<div>
|
||||
|
|
|
@ -1,32 +1,23 @@
|
|||
import { connect } from 'react-redux';
|
||||
import * as settings from 'constants/settings';
|
||||
import { doRemoveUnreadSubscription } from 'redux/actions/subscriptions';
|
||||
import { doSetClientSetting } from 'redux/actions/settings';
|
||||
import { doSetContentHistoryItem } from 'redux/actions/content';
|
||||
import {
|
||||
doFetchFileInfo,
|
||||
makeSelectClaimIsMine,
|
||||
makeSelectFileInfoForUri,
|
||||
makeSelectClaimForUri,
|
||||
makeSelectContentTypeForUri,
|
||||
makeSelectMetadataForUri,
|
||||
makeSelectChannelForClaimUri,
|
||||
selectBalance,
|
||||
makeSelectTitleForUri,
|
||||
makeSelectThumbnailForUri,
|
||||
makeSelectClaimIsNsfw,
|
||||
doPrepareEdit,
|
||||
makeSelectMediaTypeForUri,
|
||||
} from 'lbry-redux';
|
||||
import { doFetchViewCount, makeSelectViewCountForUri, makeSelectCostInfoForUri, doFetchCostInfoForUri } from 'lbryinc';
|
||||
import { selectShowMatureContent, makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { doFetchViewCount, makeSelectCostInfoForUri, doFetchCostInfoForUri } from 'lbryinc';
|
||||
import { selectShowMatureContent } 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) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
contentType: makeSelectContentTypeForUri(props.uri)(state),
|
||||
costInfo: makeSelectCostInfoForUri(props.uri)(state),
|
||||
metadata: makeSelectMetadataForUri(props.uri)(state),
|
||||
obscureNsfw: !selectShowMatureContent(state),
|
||||
|
@ -34,20 +25,13 @@ const select = (state, props) => ({
|
|||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
isSubscribed: makeSelectIsSubscribed(props.uri)(state),
|
||||
channelUri: makeSelectChannelForClaimUri(props.uri, true)(state),
|
||||
viewCount: makeSelectViewCountForUri(props.uri)(state),
|
||||
balance: selectBalance(state),
|
||||
title: makeSelectTitleForUri(props.uri)(state),
|
||||
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
||||
nsfw: makeSelectClaimIsNsfw(props.uri)(state),
|
||||
supportOption: makeSelectClientSetting(settings.SUPPORT_OPTION)(state),
|
||||
mediaType: makeSelectMediaTypeForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
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, fs)),
|
||||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||
setViewed: uri => dispatch(doSetContentHistoryItem(uri)),
|
||||
markSubscriptionRead: (channel, uri) => dispatch(doRemoveUnreadSubscription(channel, uri)),
|
||||
fetchViewCount: claimId => dispatch(doFetchViewCount(claimId)),
|
||||
|
|
|
@ -1,31 +1,14 @@
|
|||
// @flow
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import * as icons from 'constants/icons';
|
||||
import * as React from 'react';
|
||||
import { buildURI, normalizeURI } from 'lbry-redux';
|
||||
import FileViewerInitiator from 'component/fileViewerInitiator';
|
||||
import FilePrice from 'component/filePrice';
|
||||
import FileDetails from 'component/fileDetails';
|
||||
import FileActions from 'component/fileActions';
|
||||
import DateTime from 'component/dateTime';
|
||||
import Button from 'component/button';
|
||||
import Page from 'component/page';
|
||||
import FileDownloadLink from 'component/fileDownloadLink';
|
||||
import RecommendedContent from 'component/recommendedContent';
|
||||
|
||||
import CommentsList from 'component/commentsList';
|
||||
import CommentCreate from 'component/commentCreate';
|
||||
import ClaimUri from 'component/claimUri';
|
||||
import ClaimPreview from 'component/claimPreview';
|
||||
import HelpLink from 'component/common/help-link';
|
||||
import I18nMessage from 'component/i18nMessage/view';
|
||||
|
||||
export const FILE_WRAPPER_CLASS = 'grid-area--content';
|
||||
import LayoutWrapperFile from 'component/layoutWrapperFile';
|
||||
import LayoutWrapperText from 'component/layoutWrapperText';
|
||||
|
||||
type Props = {
|
||||
claim: StreamClaim,
|
||||
fileInfo: FileListItem,
|
||||
contentType: string,
|
||||
uri: string,
|
||||
claimIsMine: boolean,
|
||||
costInfo: ?{ cost: number },
|
||||
|
@ -36,14 +19,10 @@ type Props = {
|
|||
isSubscribed: boolean,
|
||||
channelUri: string,
|
||||
viewCount: number,
|
||||
prepareEdit: ({}, string, {}) => void,
|
||||
openModal: (id: string, { uri: string, claimIsMine?: boolean, isSupport?: boolean }) => void,
|
||||
markSubscriptionRead: (string, string) => void,
|
||||
fetchViewCount: string => void,
|
||||
balance: number,
|
||||
title: string,
|
||||
nsfw: boolean,
|
||||
supportOption: boolean,
|
||||
mediaType: string,
|
||||
};
|
||||
|
||||
class FilePage extends React.Component<Props> {
|
||||
|
@ -96,160 +75,25 @@ class FilePage extends React.Component<Props> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
claim,
|
||||
contentType,
|
||||
uri,
|
||||
openModal,
|
||||
claimIsMine,
|
||||
prepareEdit,
|
||||
costInfo,
|
||||
fileInfo,
|
||||
channelUri,
|
||||
viewCount,
|
||||
balance,
|
||||
title,
|
||||
nsfw,
|
||||
supportOption,
|
||||
} = this.props;
|
||||
// File info
|
||||
const { signing_channel: signingChannel } = claim;
|
||||
const channelName = signingChannel && signingChannel.name;
|
||||
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
|
||||
// We will select the claim id before they publish
|
||||
let editUri;
|
||||
if (claimIsMine) {
|
||||
const uriObject: { streamName: string, streamClaimId: string, channelName?: string } = {
|
||||
streamName: claim.name,
|
||||
streamClaimId: claim.claim_id,
|
||||
};
|
||||
if (channelName) {
|
||||
uriObject.channelName = channelName;
|
||||
}
|
||||
|
||||
editUri = buildURI(uriObject);
|
||||
}
|
||||
|
||||
const { uri, claimIsMine, costInfo, fileInfo, balance, mediaType } = this.props;
|
||||
const insufficientCredits = !claimIsMine && costInfo && costInfo.cost > balance;
|
||||
|
||||
return (
|
||||
<Page className="main--file-page">
|
||||
<ClaimUri uri={uri} />
|
||||
|
||||
<div className={`card ${FILE_WRAPPER_CLASS}`}>
|
||||
{!fileInfo && insufficientCredits && (
|
||||
<div className="media__insufficient-credits help--warning">
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
reward_link: <Button button="link" navigate="/$/rewards" label={__('Rewards')} />,
|
||||
}}
|
||||
>
|
||||
The publisher has chosen to charge LBC to view this content. Your balance is currently too low to view
|
||||
it. Check out %reward_link% for free LBC or send more LBC to your wallet.
|
||||
</I18nMessage>
|
||||
</div>
|
||||
)}
|
||||
<FileViewerInitiator uri={uri} insufficientCredits={insufficientCredits} />
|
||||
</div>
|
||||
|
||||
<div className="media__title">
|
||||
<span className="media__title-badge">
|
||||
{nsfw && <span className="badge badge--tag-mature">{__('Mature')}</span>}
|
||||
</span>
|
||||
<span className="media__title-badge">
|
||||
<FilePrice badge uri={normalizeURI(uri)} />
|
||||
</span>
|
||||
<h1 className="media__title-text">{title}</h1>
|
||||
</div>
|
||||
|
||||
<div className="columns">
|
||||
<div className="grid-area--info">
|
||||
<div className="media__subtitle--between">
|
||||
<DateTime uri={uri} show={DateTime.SHOW_DATE} />
|
||||
<span>
|
||||
{viewCount !== 1 ? __('%view_count% Views', { view_count: viewCount }) : __('1 View')}
|
||||
<HelpLink href="https://lbry.com/faq/views" />
|
||||
</span>
|
||||
</div>
|
||||
<div className="media__actions">
|
||||
<div className="section__actions">
|
||||
{claimIsMine && (
|
||||
<Button
|
||||
button="alt"
|
||||
icon={icons.EDIT}
|
||||
label={__('Edit')}
|
||||
navigate="/$/publish"
|
||||
onClick={() => {
|
||||
prepareEdit(claim, editUri, fileInfo);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
button="alt"
|
||||
icon={icons.SHARE}
|
||||
label={__('Share')}
|
||||
onClick={() => openModal(MODALS.SOCIAL_SHARE, { uri, webShareable })}
|
||||
/>
|
||||
{!claimIsMine && (
|
||||
<Button
|
||||
button="alt"
|
||||
icon={icons.TIP}
|
||||
label={__('Tip')}
|
||||
requiresAuth={IS_WEB}
|
||||
title={__('Send a tip to this creator')}
|
||||
onClick={() => openModal(MODALS.SEND_TIP, { uri, claimIsMine, isSupport: false })}
|
||||
/>
|
||||
)}
|
||||
{(claimIsMine || (!claimIsMine && supportOption)) && (
|
||||
<Button
|
||||
button="alt"
|
||||
icon={icons.SUPPORT}
|
||||
label={__('Support')}
|
||||
requiresAuth={IS_WEB}
|
||||
title={__('Support this claim')}
|
||||
onClick={() => openModal(MODALS.SEND_TIP, { uri, claimIsMine, isSupport: true })}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="section__actions">
|
||||
{/* @if TARGET='app' */}
|
||||
<FileDownloadLink uri={uri} />
|
||||
{/* @endif */}
|
||||
<FileActions uri={uri} claimId={claim.claim_id} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="section__divider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
{channelUri ? (
|
||||
<ClaimPreview uri={channelUri} type="inline" properties={false} hideBlock />
|
||||
) : (
|
||||
<div className="claim-preview--inline claim-preview-title">{__('Anonymous')}</div>
|
||||
)}
|
||||
|
||||
<FileDetails uri={uri} />
|
||||
|
||||
<div className="section__divider">
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div className="section__title--small">{__('Comments')}</div>
|
||||
<section className="section">
|
||||
<CommentCreate uri={uri} />
|
||||
</section>
|
||||
<section className="section">
|
||||
<CommentsList uri={uri} />
|
||||
</section>
|
||||
{!fileInfo && insufficientCredits && (
|
||||
<div className="media__insufficient-credits help--warning">
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
reward_link: <Button button="link" navigate="/$/rewards" label={__('Rewards')} />,
|
||||
}}
|
||||
>
|
||||
The publisher has chosen to charge LBC to view this content. Your balance is currently too low to view it.
|
||||
Check out %reward_link% for free LBC or send more LBC to your wallet.
|
||||
</I18nMessage>
|
||||
</div>
|
||||
<div className="grid-area--related">
|
||||
<RecommendedContent uri={uri} claimId={claim.claim_id} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mediaType === 'text' ? <LayoutWrapperText uri={uri} /> : <LayoutWrapperFile uri={uri} />}
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,10 +17,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.claim-list__header--small {
|
||||
color: var(--color-text-subtitle);
|
||||
}
|
||||
|
||||
.claim-list__dropdown {
|
||||
padding: 0 var(--spacing-medium);
|
||||
|
||||
|
@ -139,7 +135,6 @@
|
|||
.claim-preview--inline {
|
||||
padding: 0;
|
||||
border-bottom: none;
|
||||
margin-bottom: var(--spacing-medium);
|
||||
|
||||
.channel-thumbnail {
|
||||
width: var(--channel-thumbnail-width--small);
|
||||
|
|
|
@ -102,6 +102,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.content__cover--hidden-for-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content__loading {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
max-height: var(--inline-player-max-height);
|
||||
}
|
||||
|
||||
.file-render--document {
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.file-render__viewer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -26,12 +30,14 @@
|
|||
.file-render__viewer--document {
|
||||
@extend .file-render__viewer;
|
||||
overflow: auto;
|
||||
background-color: var(--color-file-viewer-background);
|
||||
|
||||
.markdown-preview {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: var(--spacing-large);
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
padding: var(--spacing-small);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -119,10 +119,6 @@
|
|||
border-radius: 1.5rem;
|
||||
margin-left: var(--spacing-small);
|
||||
|
||||
svg {
|
||||
stroke: var(--color-text);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-alt);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
.main {
|
||||
position: relative;
|
||||
width: calc(100% - var(--side-nav-width) - var(--spacing-large));
|
||||
margin-right: var(--spacing-main-padding);
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
width: 100%;
|
||||
|
@ -101,3 +100,12 @@
|
|||
.main--full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main__document-wrapper {
|
||||
width: 60%;
|
||||
margin: auto;
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,33 @@
|
|||
font-size: inherit;
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: var(--spacing-medium);
|
||||
padding-top: var(--spacing-medium);
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: var(--spacing-large);
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
h2 {
|
||||
font-size: 1.7em;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
h4 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
h5 {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
h6 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
// Paragraphs
|
||||
|
@ -28,10 +54,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Strikethrough text
|
||||
del {
|
||||
}
|
||||
|
||||
// Tables
|
||||
table {
|
||||
margin-bottom: 1.2rem;
|
||||
|
@ -54,6 +76,13 @@
|
|||
img {
|
||||
margin-bottom: var(--spacing-medium);
|
||||
padding-top: var(--spacing-medium);
|
||||
max-height: 40vh;
|
||||
object-position: left;
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
max-height: 30vh;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
// Horizontal Rule
|
||||
|
|
|
@ -108,3 +108,7 @@
|
|||
justify-content: space-between;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.media__document-thumbnail {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
.navigation {
|
||||
width: var(--side-nav-width);
|
||||
font-size: var(--font-body);
|
||||
margin-left: var(--spacing-main-padding);
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
display: none;
|
||||
|
|
|
@ -27,3 +27,8 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder--text-document {
|
||||
@include placeholder;
|
||||
height: 60vh;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,11 @@ html {
|
|||
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-background);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
h1,
|
||||
|
@ -30,8 +35,6 @@ h6 {
|
|||
}
|
||||
|
||||
p {
|
||||
font-size: var(--font-body);
|
||||
|
||||
& + p {
|
||||
margin-top: var(--spacing-small);
|
||||
}
|
||||
|
@ -43,7 +46,7 @@ ol {
|
|||
|
||||
li {
|
||||
list-style-position: outside;
|
||||
margin: var(--spacing-medium);
|
||||
margin: var(--spacing-xsmall) var(--spacing-medium);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
--color-link: var(--color-primary);
|
||||
--color-link-hover: #60e1ba;
|
||||
--color-link-active: #60e1ba;
|
||||
--color-link-icon: #89939e;
|
||||
--color-navigation-link: var(--color-link-icon);
|
||||
--color-link-icon: #6a7580;
|
||||
--color-navigation-link: #b3bcc6;
|
||||
--color-button-primary-bg: var(--color-primary-alt);
|
||||
--color-button-primary-bg-hover: #44796c;
|
||||
--color-button-primary-text: var(--color-primary);
|
||||
|
|
Loading…
Reference in a new issue