new text viewer layout

This commit is contained in:
Sean Yesmunt 2020-01-06 13:32:35 -05:00
parent 9a6f2a1975
commit 72b9f3efdd
47 changed files with 658 additions and 274 deletions

View file

@ -10,7 +10,7 @@ import ReactModal from 'react-modal';
import { openContextMenu } from 'util/context-menu'; import { openContextMenu } from 'util/context-menu';
import useKonamiListener from 'util/enhanced-layout'; import useKonamiListener from 'util/enhanced-layout';
import Yrbl from 'component/yrbl'; import Yrbl from 'component/yrbl';
import FileViewer from 'component/fileViewer'; import FloatingViewer from 'component/floatingViewer';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import usePrevious from 'effects/use-previous'; import usePrevious from 'effects/use-previous';
import Nag from 'component/common/nag'; import Nag from 'component/common/nag';
@ -203,7 +203,7 @@ function App(props: Props) {
> >
<Router /> <Router />
<ModalRouter /> <ModalRouter />
<FileViewer pageUri={uri} /> <FloatingViewer pageUri={uri} />
{/* @if TARGET='web' */} {/* @if TARGET='web' */}
<YoutubeWelcome /> <YoutubeWelcome />

View file

@ -147,6 +147,7 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
}} }}
className={combinedClassName} className={combinedClassName}
activeClassName={activeClass} activeClassName={activeClass}
{...otherProps}
> >
{content} {content}
</NavLink> </NavLink>

View file

@ -101,7 +101,7 @@ export default function ClaimList(props: Props) {
{header !== false && ( {header !== false && (
<React.Fragment> <React.Fragment>
{headerLabel && <label className="claim-list__header-label">{headerLabel}</label>} {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} {header}
{loading && <Spinner type="small" />} {loading && <Spinner type="small" />}
<div className="claim-list__alt-controls"> <div className="claim-list__alt-controls">

View file

@ -7,7 +7,7 @@ import { withRouter } from 'react-router-dom';
import { openCopyLinkMenu } from 'util/context-menu'; import { openCopyLinkMenu } from 'util/context-menu';
import { formatLbryUrlForWeb } from 'util/url'; import { formatLbryUrlForWeb } from 'util/url';
import { isEmpty } from 'util/object'; import { isEmpty } from 'util/object';
import CardMedia from 'component/cardMedia'; import FileThumbnail from 'component/fileThumbnail';
import UriIndicator from 'component/uriIndicator'; import UriIndicator from 'component/uriIndicator';
import TruncatedText from 'component/common/truncated-text'; import TruncatedText from 'component/common/truncated-text';
import DateTime from 'component/dateTime'; import DateTime from 'component/dateTime';
@ -200,7 +200,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
<ChannelThumbnail uri={uri} obscure={channelIsBlocked} /> <ChannelThumbnail uri={uri} obscure={channelIsBlocked} />
</UriIndicator> </UriIndicator>
) : ( ) : (
<CardMedia thumbnail={thumbnail} /> <FileThumbnail thumbnail={thumbnail} />
)} )}
<div className="claim-preview__text"> <div className="claim-preview__text">
<div className="claim-preview-metadata"> <div className="claim-preview-metadata">

View file

@ -1,21 +1,33 @@
import { connect } from 'react-redux'; 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 { makeSelectCostInfoForUri } from 'lbryinc';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doOpenModal } from 'redux/actions/app'; import { doOpenModal } from 'redux/actions/app';
import FileActions from './view'; import fs from 'fs';
import FilePage from './view';
const select = (state, props) => ({ const select = (state, props) => ({
fileInfo: makeSelectFileInfoForUri(props.uri)(state), claim: makeSelectClaimForUri(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),
claimIsMine: makeSelectClaimIsMine(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 => ({ const perform = dispatch => ({
openModal: (modal, props) => dispatch(doOpenModal(modal, props)), openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
prepareEdit: (publishData, uri, fileInfo) => dispatch(doPrepareEdit(publishData, uri, fileInfo, fs)),
}); });
export default connect( export default connect(
select, select,
perform perform
)(FileActions); )(FilePage);

View file

@ -3,40 +3,114 @@ import * as MODALS from 'constants/modal_types';
import * as ICONS from 'constants/icons'; 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 Tooltip from 'component/common/tooltip'; import FileDownloadLink from 'component/fileDownloadLink';
import { buildURI } from 'lbry-redux';
type Props = { type Props = {
uri: string, uri: string,
claimId: string, claim: StreamClaim,
openModal: (id: string, { uri: string }) => void, openModal: (id: string, { uri: string, claimIsMine?: boolean, isSupport?: boolean }) => void,
prepareEdit: ({}, string, {}) => void,
claimIsMine: boolean, claimIsMine: boolean,
fileInfo: FileListItem, fileInfo: FileListItem,
costInfo: ?{ cost: number },
contentType: string,
supportOption: boolean,
}; };
class FileActions extends React.PureComponent<Props> { function FileActions(props: Props) {
render() { const { fileInfo, uri, openModal, claimIsMine, claim, costInfo, contentType, supportOption, prepareEdit } = props;
const { fileInfo, uri, openModal, claimIsMine, claimId } = this.props; const webShareable =
const showDelete = claimIsMine || (fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed > 0)); costInfo && costInfo.cost === 0 && contentType && ['video', 'image', 'audio'].includes(contentType.split('/')[0]);
return ( const showDelete = claimIsMine || (fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed > 0));
<React.Fragment> 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 && ( {showDelete && (
<Tooltip label={__('Remove from your library')}> <Button
<Button title={__('Remove from your library')}
button="alt" button="alt"
icon={ICONS.DELETE} icon={ICONS.DELETE}
description={__('Delete')} description={__('Delete')}
onClick={() => openModal(MODALS.CONFIRM_FILE_REMOVE, { uri })} onClick={() => openModal(MODALS.CONFIRM_FILE_REMOVE, { uri })}
/> />
</Tooltip>
)} )}
{!claimIsMine && ( {!claimIsMine && (
<Tooltip label={__('Report content')}> <Button
<Button button="alt" icon={ICONS.REPORT} href={`https://lbry.com/dmca/${claimId}`} /> title={__('Report content')}
</Tooltip> button="alt"
icon={ICONS.REPORT}
href={`https://lbry.com/dmca/${claimId}`}
/>
)} )}
</React.Fragment> </div>
); </div>
} );
} }
export default FileActions; export default FileActions;

View 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);

View 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;

View file

@ -43,11 +43,9 @@ class FileDetails extends PureComponent<Props> {
<Fragment> <Fragment>
<Expandable> <Expandable>
{description && ( {description && (
<Fragment> <div className="media__info-text">
<div className="media__info-text"> <MarkdownPreview content={description} />
<MarkdownPreview content={description} /> </div>
</div>
</Fragment>
)} )}
<ClaimTags uri={uri} type="large" /> <ClaimTags uri={uri} type="large" />
<table className="table table--condensed table--fixed table--file-details"> <table className="table table--condensed table--fixed table--file-details">

View file

@ -1,6 +1,7 @@
// @flow // @flow
import { remote } from 'electron'; import { remote } from 'electron';
import React, { Suspense, Fragment } from 'react'; import React, { Suspense, Fragment } from 'react';
import classnames from 'classnames';
import LoadingScreen from 'component/common/loading-screen'; import LoadingScreen from 'component/common/loading-screen';
import VideoViewer from 'component/viewers/videoViewer'; import VideoViewer from 'component/viewers/videoViewer';
import ImageViewer from 'component/viewers/imageViewer'; import ImageViewer from 'component/viewers/imageViewer';
@ -186,8 +187,10 @@ class FileRender extends React.PureComponent<Props> {
} }
render() { render() {
const { mediaType } = this.props;
return ( return (
<div className="file-render"> <div className={classnames('file-render', { 'file-render--document': mediaType === 'text' })}>
<Suspense fallback={<div />}>{this.renderViewer()}</Suspense> <Suspense fallback={<div />}>{this.renderViewer()}</Suspense>
</div> </div>
); );

View file

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -21,11 +21,11 @@ class CardMedia extends React.PureComponent<Props> {
// @if TARGET='web' // @if TARGET='web'
// Pass image urls through a compression proxy // Pass image urls through a compression proxy
url = thumbnail || Placeholder; url = thumbnail || Placeholder;
// url = thumbnail // url = thumbnail
// ? 'https://ext.thumbnails.lbry.com/400x,q55/' + // ? 'https://ext.thumbnails.lbry.com/400x,q55/' +
// The image server will redirect if we don't remove the double slashes after http(s) // The image server will redirect if we don't remove the double slashes after http(s)
// thumbnail.replace('https://', 'https:/').replace('http://', 'http:/') // thumbnail.replace('https://', 'https:/').replace('http://', 'http:/')
// : Placeholder; // : Placeholder;
// @endif // @endif
// @if TARGET='app' // @if TARGET='app'
url = thumbnail || Placeholder; url = thumbnail || Placeholder;

View 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);

View 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;

View file

@ -1,6 +1,6 @@
// @flow // @flow
// This component is entirely for triggering the start of a file view // 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 // 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 // while a file is currently being viewed
import React, { useEffect, useCallback, Fragment } from 'react'; import React, { useEffect, useCallback, Fragment } from 'react';
@ -27,9 +27,10 @@ type Props = {
hasCostInfo: boolean, hasCostInfo: boolean,
costInfo: any, costInfo: any,
isAutoPlayable: boolean, isAutoPlayable: boolean,
inline: boolean,
}; };
export default function FileViewer(props: Props) { export default function FileViewerInitiator(props: Props) {
const { const {
play, play,
mediaType, mediaType,
@ -49,6 +50,7 @@ export default function FileViewer(props: Props) {
const cost = costInfo && costInfo.cost; const cost = costInfo && costInfo.cost;
const forceVideo = ['application/x-ext-mkv', 'video/x-matroska'].includes(contentType); const forceVideo = ['application/x-ext-mkv', 'video/x-matroska'].includes(contentType);
const isPlayable = ['audio', 'video'].includes(mediaType) || forceVideo; const isPlayable = ['audio', 'video'].includes(mediaType) || forceVideo;
const isText = mediaType === 'text';
const fileStatus = fileInfo && fileInfo.status; const fileStatus = fileInfo && fileInfo.status;
const webStreamOnly = contentType === 'application/pdf' || mediaType === 'text'; const webStreamOnly = contentType === 'application/pdf' || mediaType === 'text';
const supported = IS_WEB ? (!cost && isStreamable) || webStreamOnly || forceVideo : true; const supported = IS_WEB ? (!cost && isStreamable) || webStreamOnly || forceVideo : true;
@ -95,10 +97,10 @@ export default function FileViewer(props: Props) {
useEffect(() => { useEffect(() => {
const videoOnPage = document.querySelector('video'); const videoOnPage = document.querySelector('video');
if (autoplay && !videoOnPage && isAutoPlayable && hasCostInfo && cost === 0) { if (((autoplay && !videoOnPage && isAutoPlayable) || isText) && hasCostInfo && cost === 0) {
viewFile(); viewFile();
} }
}, [autoplay, viewFile, isAutoPlayable, hasCostInfo, cost]); }, [autoplay, viewFile, isAutoPlayable, hasCostInfo, cost, isText]);
return ( return (
<div <div
@ -108,6 +110,7 @@ export default function FileViewer(props: Props) {
className={classnames({ className={classnames({
content__cover: supported, content__cover: supported,
'content__cover--disabled': !supported, 'content__cover--disabled': !supported,
'content__cover--hidden-for-text': isText,
'card__media--nsfw': obscurePreview, 'card__media--nsfw': obscurePreview,
'card__media--disabled': supported && !fileInfo && insufficientCredits, 'card__media--disabled': supported && !fileInfo && insufficientCredits,
})} })}
@ -127,6 +130,7 @@ export default function FileViewer(props: Props) {
} }
/> />
)} )}
{!isPlaying && supported && ( {!isPlaying && supported && (
<Button <Button
onClick={viewFile} onClick={viewFile}

View file

@ -8,7 +8,7 @@ import FileRender from 'component/fileRender';
import UriIndicator from 'component/uriIndicator'; import UriIndicator from 'component/uriIndicator';
import usePersistedState from 'effects/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
import usePrevious from 'effects/use-previous'; 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 Draggable from 'react-draggable';
import Tooltip from 'component/common/tooltip'; import Tooltip from 'component/common/tooltip';
import { onFullscreenChange } from 'util/full-screen'; import { onFullscreenChange } from 'util/full-screen';
@ -96,7 +96,6 @@ export default function FileViewer(props: Props) {
function handleResize() { function handleResize() {
const element = document.querySelector(`.${FILE_WRAPPER_CLASS}`); const element = document.querySelector(`.${FILE_WRAPPER_CLASS}`);
if (!element) { if (!element) {
console.error("Can't find file viewer wrapper to attach to the inline viewer to"); // eslint-disable-line
return; return;
} }
@ -125,7 +124,10 @@ export default function FileViewer(props: Props) {
} }
const hidePlayer = const hidePlayer =
!isPlaying || !uri || (!inline && (isMobile || !floatingPlayerEnabled || !['audio', 'video'].includes(mediaType))); mediaType === 'text' ||
!isPlaying ||
!uri ||
(!inline && (isMobile || !floatingPlayerEnabled || !['audio', 'video'].includes(mediaType)));
if (hidePlayer) { if (hidePlayer) {
return null; return null;

View 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);

View 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;

View 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);

View 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;

View 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)
);

View 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>
);
}

View file

@ -98,12 +98,11 @@ class DocumentViewer extends React.PureComponent<Props, State> {
render() { render() {
const { error, loading, content } = this.state; const { error, loading, content } = this.state;
const isReady = content && !error; const isReady = content && !error;
const loadingMessage = __('Rendering document.');
const errorMessage = __("Sorry, looks like we can't load the document."); const errorMessage = __("Sorry, looks like we can't load the document.");
return ( return (
<div className="file-render__viewer--document"> <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} />} {error && <LoadingScreen status={errorMessage} spinner={!error} />}
{isReady && this.renderDocument()} {isReady && this.renderDocument()}
</div> </div>

View file

@ -1,7 +1,7 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import CardMedia from 'component/cardMedia'; import FileThumbnail from 'component/fileThumbnail';
type Props = { type Props = {
params: UpdatePublishFormData, params: UpdatePublishFormData,
progress: string, progress: string,
@ -13,7 +13,7 @@ export default function WebUploadItem(props: Props) {
return ( return (
<li className={'claim-preview claim-preview--inactive card--inline'}> <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-metadata'}>
<div className="claim-preview-info"> <div className="claim-preview-info">
<div className="claim-preview-title">{params.title}</div> <div className="claim-preview-title">{params.title}</div>

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import WalletAddress from './node_modules/component/walletAddress'; import WalletAddress from 'component/walletAddress';
import Page from './node_modules/component/page'; import Page from 'component/page';
const WalletAddressPage = () => ( const WalletAddressPage = () => (
<Page className="main--contained"> <Page className="main--contained">

View file

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import WalletSend from './node_modules/component/walletSend'; import WalletSend from 'component/walletSend';
const WalletSendModal = () => ( const WalletSendModal = () => (
<div> <div>

View file

@ -1,32 +1,23 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import * as settings from 'constants/settings';
import { doRemoveUnreadSubscription } from 'redux/actions/subscriptions'; import { doRemoveUnreadSubscription } from 'redux/actions/subscriptions';
import { doSetClientSetting } from 'redux/actions/settings';
import { doSetContentHistoryItem } from 'redux/actions/content'; import { doSetContentHistoryItem } from 'redux/actions/content';
import { import {
doFetchFileInfo, doFetchFileInfo,
makeSelectClaimIsMine, makeSelectClaimIsMine,
makeSelectFileInfoForUri, makeSelectFileInfoForUri,
makeSelectClaimForUri, makeSelectClaimForUri,
makeSelectContentTypeForUri,
makeSelectMetadataForUri, makeSelectMetadataForUri,
makeSelectChannelForClaimUri, makeSelectChannelForClaimUri,
selectBalance, selectBalance,
makeSelectTitleForUri, makeSelectMediaTypeForUri,
makeSelectThumbnailForUri,
makeSelectClaimIsNsfw,
doPrepareEdit,
} from 'lbry-redux'; } from 'lbry-redux';
import { doFetchViewCount, makeSelectViewCountForUri, makeSelectCostInfoForUri, doFetchCostInfoForUri } from 'lbryinc'; import { doFetchViewCount, makeSelectCostInfoForUri, doFetchCostInfoForUri } from 'lbryinc';
import { selectShowMatureContent, makeSelectClientSetting } from 'redux/selectors/settings'; import { selectShowMatureContent } from 'redux/selectors/settings';
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions'; import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
import { doOpenModal } from 'redux/actions/app';
import fs from 'fs';
import FilePage from './view'; import FilePage from './view';
const select = (state, props) => ({ const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state), claim: makeSelectClaimForUri(props.uri)(state),
contentType: makeSelectContentTypeForUri(props.uri)(state),
costInfo: makeSelectCostInfoForUri(props.uri)(state), costInfo: makeSelectCostInfoForUri(props.uri)(state),
metadata: makeSelectMetadataForUri(props.uri)(state), metadata: makeSelectMetadataForUri(props.uri)(state),
obscureNsfw: !selectShowMatureContent(state), obscureNsfw: !selectShowMatureContent(state),
@ -34,20 +25,13 @@ const select = (state, props) => ({
claimIsMine: makeSelectClaimIsMine(props.uri)(state), claimIsMine: makeSelectClaimIsMine(props.uri)(state),
isSubscribed: makeSelectIsSubscribed(props.uri)(state), isSubscribed: makeSelectIsSubscribed(props.uri)(state),
channelUri: makeSelectChannelForClaimUri(props.uri, true)(state), channelUri: makeSelectChannelForClaimUri(props.uri, true)(state),
viewCount: makeSelectViewCountForUri(props.uri)(state),
balance: selectBalance(state), balance: selectBalance(state),
title: makeSelectTitleForUri(props.uri)(state), mediaType: makeSelectMediaTypeForUri(props.uri)(state),
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
nsfw: makeSelectClaimIsNsfw(props.uri)(state),
supportOption: makeSelectClientSetting(settings.SUPPORT_OPTION)(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)), fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(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)), setViewed: uri => dispatch(doSetContentHistoryItem(uri)),
markSubscriptionRead: (channel, uri) => dispatch(doRemoveUnreadSubscription(channel, uri)), markSubscriptionRead: (channel, uri) => dispatch(doRemoveUnreadSubscription(channel, uri)),
fetchViewCount: claimId => dispatch(doFetchViewCount(claimId)), fetchViewCount: claimId => dispatch(doFetchViewCount(claimId)),

View file

@ -1,31 +1,14 @@
// @flow // @flow
import * as MODALS from 'constants/modal_types';
import * as icons from 'constants/icons';
import * as React from 'react'; 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 Button from 'component/button';
import Page from 'component/page'; 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'; import I18nMessage from 'component/i18nMessage/view';
import LayoutWrapperFile from 'component/layoutWrapperFile';
export const FILE_WRAPPER_CLASS = 'grid-area--content'; import LayoutWrapperText from 'component/layoutWrapperText';
type Props = { type Props = {
claim: StreamClaim, claim: StreamClaim,
fileInfo: FileListItem, fileInfo: FileListItem,
contentType: string,
uri: string, uri: string,
claimIsMine: boolean, claimIsMine: boolean,
costInfo: ?{ cost: number }, costInfo: ?{ cost: number },
@ -36,14 +19,10 @@ type Props = {
isSubscribed: boolean, isSubscribed: boolean,
channelUri: string, channelUri: string,
viewCount: number, viewCount: number,
prepareEdit: ({}, string, {}) => void,
openModal: (id: string, { uri: string, claimIsMine?: boolean, isSupport?: boolean }) => void,
markSubscriptionRead: (string, string) => void, markSubscriptionRead: (string, string) => void,
fetchViewCount: string => void, fetchViewCount: string => void,
balance: number, balance: number,
title: string, mediaType: string,
nsfw: boolean,
supportOption: boolean,
}; };
class FilePage extends React.Component<Props> { class FilePage extends React.Component<Props> {
@ -96,160 +75,25 @@ class FilePage extends React.Component<Props> {
} }
render() { render() {
const { const { uri, claimIsMine, costInfo, fileInfo, balance, mediaType } = this.props;
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 insufficientCredits = !claimIsMine && costInfo && costInfo.cost > balance; const insufficientCredits = !claimIsMine && costInfo && costInfo.cost > balance;
return ( return (
<Page className="main--file-page"> <Page className="main--file-page">
<ClaimUri uri={uri} /> {!fileInfo && insufficientCredits && (
<div className="media__insufficient-credits help--warning">
<div className={`card ${FILE_WRAPPER_CLASS}`}> <I18nMessage
{!fileInfo && insufficientCredits && ( tokens={{
<div className="media__insufficient-credits help--warning"> reward_link: <Button button="link" navigate="/$/rewards" label={__('Rewards')} />,
<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>
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>
</div> </div>
<div className="grid-area--related"> )}
<RecommendedContent uri={uri} claimId={claim.claim_id} />
</div> {mediaType === 'text' ? <LayoutWrapperText uri={uri} /> : <LayoutWrapperFile uri={uri} />}
</div>
</Page> </Page>
); );
} }

View file

@ -17,10 +17,6 @@
} }
} }
.claim-list__header--small {
color: var(--color-text-subtitle);
}
.claim-list__dropdown { .claim-list__dropdown {
padding: 0 var(--spacing-medium); padding: 0 var(--spacing-medium);
@ -139,7 +135,6 @@
.claim-preview--inline { .claim-preview--inline {
padding: 0; padding: 0;
border-bottom: none; border-bottom: none;
margin-bottom: var(--spacing-medium);
.channel-thumbnail { .channel-thumbnail {
width: var(--channel-thumbnail-width--small); width: var(--channel-thumbnail-width--small);

View file

@ -102,6 +102,10 @@
} }
} }
.content__cover--hidden-for-text {
display: none;
}
.content__loading { .content__loading {
height: 100%; height: 100%;
display: flex; display: flex;

View file

@ -6,6 +6,10 @@
max-height: var(--inline-player-max-height); max-height: var(--inline-player-max-height);
} }
.file-render--document {
max-height: none;
}
.file-render__viewer { .file-render__viewer {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -26,12 +30,14 @@
.file-render__viewer--document { .file-render__viewer--document {
@extend .file-render__viewer; @extend .file-render__viewer;
overflow: auto; overflow: auto;
background-color: var(--color-file-viewer-background);
.markdown-preview { .markdown-preview {
height: 100%; height: 100%;
overflow: auto; overflow: auto;
padding: var(--spacing-large);
@media (max-width: $breakpoint-small) {
padding: var(--spacing-small);
}
} }
} }

View file

@ -119,10 +119,6 @@
border-radius: 1.5rem; border-radius: 1.5rem;
margin-left: var(--spacing-small); margin-left: var(--spacing-small);
svg {
stroke: var(--color-text);
}
&:hover { &:hover {
background-color: var(--color-primary-alt); background-color: var(--color-primary-alt);
} }

View file

@ -25,7 +25,6 @@
.main { .main {
position: relative; position: relative;
width: calc(100% - var(--side-nav-width) - var(--spacing-large)); width: calc(100% - var(--side-nav-width) - var(--spacing-large));
margin-right: var(--spacing-main-padding);
@media (max-width: $breakpoint-small) { @media (max-width: $breakpoint-small) {
width: 100%; width: 100%;
@ -101,3 +100,12 @@
.main--full-width { .main--full-width {
width: 100%; width: 100%;
} }
.main__document-wrapper {
width: 60%;
margin: auto;
@media (max-width: $breakpoint-small) {
width: 100%;
}
}

View file

@ -13,7 +13,33 @@
font-size: inherit; font-size: inherit;
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
margin-bottom: var(--spacing-medium); 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 // Paragraphs
@ -28,10 +54,6 @@
} }
} }
// Strikethrough text
del {
}
// Tables // Tables
table { table {
margin-bottom: 1.2rem; margin-bottom: 1.2rem;
@ -54,6 +76,13 @@
img { img {
margin-bottom: var(--spacing-medium); margin-bottom: var(--spacing-medium);
padding-top: 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 // Horizontal Rule

View file

@ -108,3 +108,7 @@
justify-content: space-between; justify-content: space-between;
margin-top: 0; margin-top: 0;
} }
.media__document-thumbnail {
margin-top: 0;
}

View file

@ -1,6 +1,7 @@
.navigation { .navigation {
width: var(--side-nav-width); width: var(--side-nav-width);
font-size: var(--font-body); font-size: var(--font-body);
margin-left: var(--spacing-main-padding);
@media (max-width: $breakpoint-small) { @media (max-width: $breakpoint-small) {
display: none; display: none;

View file

@ -27,3 +27,8 @@
} }
} }
} }
.placeholder--text-document {
@include placeholder;
height: 60vh;
}

View file

@ -18,6 +18,11 @@ html {
color: var(--color-text); color: var(--color-text);
background-color: var(--color-background); background-color: var(--color-background);
font-size: 16px;
}
body {
font-size: 1em;
} }
h1, h1,
@ -30,8 +35,6 @@ h6 {
} }
p { p {
font-size: var(--font-body);
& + p { & + p {
margin-top: var(--spacing-small); margin-top: var(--spacing-small);
} }
@ -43,7 +46,7 @@ ol {
li { li {
list-style-position: outside; list-style-position: outside;
margin: var(--spacing-medium); margin: var(--spacing-xsmall) var(--spacing-medium);
margin-bottom: 0; margin-bottom: 0;
} }
} }

View file

@ -12,8 +12,8 @@
--color-link: var(--color-primary); --color-link: var(--color-primary);
--color-link-hover: #60e1ba; --color-link-hover: #60e1ba;
--color-link-active: #60e1ba; --color-link-active: #60e1ba;
--color-link-icon: #89939e; --color-link-icon: #6a7580;
--color-navigation-link: var(--color-link-icon); --color-navigation-link: #b3bcc6;
--color-button-primary-bg: var(--color-primary-alt); --color-button-primary-bg: var(--color-primary-alt);
--color-button-primary-bg-hover: #44796c; --color-button-primary-bg-hover: #44796c;
--color-button-primary-text: var(--color-primary); --color-button-primary-text: var(--color-primary);