Add tags to publish page, infinite scroll, navigation improvements #2593
20 changed files with 101 additions and 83 deletions
|
@ -63,7 +63,7 @@
|
|||
"@exponent/electron-cookies": "^2.0.0",
|
||||
"@hot-loader/react-dom": "16.8",
|
||||
"@lbry/color": "^1.0.2",
|
||||
"@lbry/components": "^2.7.2",
|
||||
"@lbry/components": "^2.7.4",
|
||||
"@reach/rect": "^0.2.1",
|
||||
"@reach/tabs": "^0.1.5",
|
||||
"@reach/tooltip": "^0.2.1",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectFileInfoForUri, makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { makeSelectFileInfoForUri, makeSelectClaimIsMine } from 'lbry-redux';
|
||||
import { selectRewardContentClaimIds } from 'lbryinc';
|
||||
import { makeSelectIsSubscribed, makeSelectIsNew } from 'redux/selectors/subscriptions';
|
||||
import FileProperties from './view';
|
||||
|
@ -10,7 +10,6 @@ const select = (state, props) => ({
|
|||
isSubscribed: makeSelectIsSubscribed(props.uri)(state),
|
||||
isNew: makeSelectIsNew(props.uri)(state),
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -4,10 +4,10 @@ import * as React from 'react';
|
|||
import { parseURI } from 'lbry-redux';
|
||||
import Icon from 'component/common/icon';
|
||||
import FilePrice from 'component/filePrice';
|
||||
import VideoDuration from 'component/videoDuration';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
claim: ?StreamClaim,
|
||||
downloaded: boolean,
|
||||
claimIsMine: boolean,
|
||||
isSubscribed: boolean,
|
||||
|
@ -16,32 +16,17 @@ type Props = {
|
|||
};
|
||||
|
||||
export default function FileProperties(props: Props) {
|
||||
const { claim, uri, downloaded, claimIsMine, rewardedContentClaimIds, isSubscribed } = props;
|
||||
const { uri, downloaded, claimIsMine, rewardedContentClaimIds, isSubscribed } = props;
|
||||
const { claimId } = parseURI(uri);
|
||||
const isRewardContent = rewardedContentClaimIds.includes(claimId);
|
||||
|
||||
const video = claim && claim.value && claim.value.video;
|
||||
let duration;
|
||||
if (video && video.duration) {
|
||||
// $FlowFixMe
|
||||
let date = new Date(null);
|
||||
date.setSeconds(video.duration);
|
||||
let timeString = date.toISOString().substr(11, 8);
|
||||
|
||||
if (timeString.startsWith('00:')) {
|
||||
timeString = timeString.substr(3);
|
||||
}
|
||||
|
||||
duration = timeString;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="file-properties">
|
||||
{isSubscribed && <Icon tooltip icon={icons.SUBSCRIPTION} />}
|
||||
{!claimIsMine && downloaded && <Icon tooltip icon={icons.DOWNLOAD} />}
|
||||
{isRewardContent && <Icon tooltip icon={icons.FEATURED} />}
|
||||
<FilePrice hideFree uri={uri} />
|
||||
{duration && <span className="media__subtitle">{duration}</span>}
|
||||
<VideoDuration className="media__subtitle" uri={uri} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ const select = state => ({
|
|||
channel: makeSelectPublishFormValue('channel')(state),
|
||||
bid: makeSelectPublishFormValue('bid')(state),
|
||||
uri: makeSelectPublishFormValue('uri')(state),
|
||||
bid: makeSelectPublishFormValue('bid')(state),
|
||||
isStillEditing: selectIsStillEditing(state),
|
||||
isResolvingUri: selectIsResolvingPublishUris(state),
|
||||
amountNeededForTakeover: selectTakeOverAmount(state),
|
||||
|
|
|
@ -10,7 +10,7 @@ type Props = {
|
|||
name: string,
|
||||
channel: string,
|
||||
uri: string,
|
||||
bid: string,
|
||||
bid: number,
|
||||
balance: number,
|
||||
isStillEditing: boolean,
|
||||
myClaimForUri: ?StreamClaim,
|
||||
|
@ -27,7 +27,7 @@ function PublishText(props: Props) {
|
|||
uri,
|
||||
isStillEditing,
|
||||
myClaimForUri,
|
||||
bid: bidString,
|
||||
bid,
|
||||
isResolvingUri,
|
||||
amountNeededForTakeover,
|
||||
prepareEdit,
|
||||
|
@ -37,7 +37,6 @@ function PublishText(props: Props) {
|
|||
const [nameError, setNameError] = useState(undefined);
|
||||
const [bidError, setBidError] = useState(undefined);
|
||||
const previousBidAmount = myClaimForUri && Number(myClaimForUri.amount);
|
||||
const bid = Number(bidString);
|
||||
|
||||
function editExistingClaim() {
|
||||
if (myClaimForUri) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doUpdatePublishForm } from 'redux/actions/publish';
|
||||
import { makeSelectPublishFormValue } from 'redux/selectors/publish';
|
||||
import PublishPage from './view';
|
||||
|
||||
|
@ -7,7 +8,11 @@ const select = state => ({
|
|||
fee: makeSelectPublishFormValue('fee')(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
updatePublishForm: values => dispatch(doUpdatePublishForm(values)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
null
|
||||
perform
|
||||
)(PublishPage);
|
||||
|
|
|
@ -10,7 +10,7 @@ type Props = {
|
|||
function RewardTotal(props: Props) {
|
||||
const { rewards } = props;
|
||||
const rewardTotal = rewards.reduce((acc, val) => acc + val.reward_amount, 0);
|
||||
const total = useTween(rewardTotal * 25);
|
||||
const total = useTween(rewardTotal * 40);
|
||||
const integer = Math.round(total * rewardTotal);
|
||||
|
||||
return (
|
||||
|
|
|
@ -20,6 +20,7 @@ export default function Tag(props: Props) {
|
|||
<Button
|
||||
{...clickProps}
|
||||
disabled={disabled}
|
||||
title={type === 'add' ? __('Add tag') : __('Remove tag')}
|
||||
className={classnames('tag', {
|
||||
'tag--add': type === 'add',
|
||||
'tag--remove': type === 'remove',
|
||||
|
|
|
@ -19,7 +19,7 @@ type Props = {
|
|||
};
|
||||
|
||||
export default function TagSelect(props: Props) {
|
||||
const { unfollowedTags, followedTags, doToggleTagFollow, doAddTag, onSelect } = props;
|
||||
const { unfollowedTags = [], followedTags = [], doToggleTagFollow, doAddTag, onSelect } = props;
|
||||
const [newTag, setNewTag] = useState('');
|
||||
|
||||
let tags = unfollowedTags.slice();
|
||||
|
@ -27,9 +27,14 @@ export default function TagSelect(props: Props) {
|
|||
tags.unshift({ name: newTag });
|
||||
}
|
||||
|
||||
const doesTagMatch = ({ name }) => (newTag ? name.toLowerCase().includes(newTag.toLowerCase()) : true);
|
||||
const suggestedTags = tags.filter(doesTagMatch).slice(0, 5);
|
||||
const suggestedTransitions = useTransition(suggestedTags, tag => tag.name, unfollowedTagsAnimation);
|
||||
const doesTagMatch = name => (newTag ? name.toLowerCase().includes(newTag.toLowerCase()) : true);
|
||||
// Make sure there are no duplicates, then trim
|
||||
const suggestedTagsSet = new Set(tags.map(tag => tag.name));
|
||||
const suggestedTags = Array.from(suggestedTagsSet)
|
||||
.filter(doesTagMatch)
|
||||
.slice(0, 5);
|
||||
|
||||
const suggestedTransitions = useTransition(suggestedTags, tag => tag, unfollowedTagsAnimation);
|
||||
|
||||
function onChange(e) {
|
||||
setNewTag(e.target.value);
|
||||
|
@ -42,11 +47,11 @@ export default function TagSelect(props: Props) {
|
|||
if (onSelect) {
|
||||
onSelect({ name: newTag });
|
||||
} else {
|
||||
if (!unfollowedTags.includes(newTag)) {
|
||||
if (!unfollowedTags.map(({ name }) => name).includes(newTag)) {
|
||||
doAddTag(newTag);
|
||||
}
|
||||
|
||||
if (!followedTags.includes(newTag)) {
|
||||
if (!followedTags.map(({ name }) => name).includes(newTag)) {
|
||||
doToggleTagFollow(newTag);
|
||||
}
|
||||
}
|
||||
|
@ -54,9 +59,9 @@ export default function TagSelect(props: Props) {
|
|||
|
||||
function handleTagClick(tag) {
|
||||
if (onSelect) {
|
||||
onSelect(tag);
|
||||
onSelect({ name: tag });
|
||||
} else {
|
||||
doToggleTagFollow(tag.name);
|
||||
doToggleTagFollow(tag);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,7 +79,7 @@ export default function TagSelect(props: Props) {
|
|||
<ul className="tags">
|
||||
{suggestedTransitions.map(({ item, key, props }) => (
|
||||
<animated.li key={key} style={props}>
|
||||
<Tag name={item.name} type="add" onClick={() => handleTagClick(item)} />
|
||||
<Tag name={item} type="add" onClick={() => handleTagClick(item)} />
|
||||
</animated.li>
|
||||
))}
|
||||
{!suggestedTransitions.length && <p className="empty tags__empty-message">No suggested tags</p>}
|
||||
|
|
12
src/ui/component/videoDuration/index.js
Normal file
12
src/ui/component/videoDuration/index.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||
import VideoDuration from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
null
|
||||
)(VideoDuration);
|
30
src/ui/component/videoDuration/view.jsx
Normal file
30
src/ui/component/videoDuration/view.jsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
claim: ?StreamClaim,
|
||||
className?: string,
|
||||
};
|
||||
|
||||
function VideoDuration(props: Props) {
|
||||
const { claim, className } = props;
|
||||
|
||||
const video = claim && claim.value && claim.value.video;
|
||||
let duration;
|
||||
if (video && video.duration) {
|
||||
// $FlowFixMe
|
||||
let date = new Date(null);
|
||||
date.setSeconds(video.duration);
|
||||
let timeString = date.toISOString().substr(11, 8);
|
||||
|
||||
if (timeString.startsWith('00:')) {
|
||||
timeString = timeString.substr(3);
|
||||
}
|
||||
|
||||
duration = timeString;
|
||||
}
|
||||
|
||||
return duration ? <span className={className}>{duration}</span> : null;
|
||||
}
|
||||
|
||||
export default VideoDuration;
|
|
@ -9,9 +9,7 @@ import InvitePage from 'page/invite';
|
|||
|
||||
const WalletPage = () => (
|
||||
<Page>
|
||||
<UserEmail />
|
||||
|
||||
{IS_WEB && <UnsupportedOnWeb />}
|
||||
<UnsupportedOnWeb />
|
||||
<div className={classnames({ 'card--disabled': IS_WEB })}>
|
||||
<div className="columns">
|
||||
<UserEmail />
|
||||
|
|
|
@ -22,6 +22,7 @@ import RecommendedContent from 'component/recommendedContent';
|
|||
import ClaimTags from 'component/claimTags';
|
||||
import CommentsList from 'component/commentsList';
|
||||
import CommentCreate from 'component/commentCreate';
|
||||
import VideoDuration from 'component/videoDuration';
|
||||
|
||||
type Props = {
|
||||
claim: StreamClaim,
|
||||
|
@ -184,21 +185,6 @@ class FilePage extends React.Component<Props> {
|
|||
|
||||
const insufficientCredits = !claimIsMine && costInfo && costInfo.cost > balance;
|
||||
|
||||
const video = claim && claim.value && claim.value.video;
|
||||
let duration;
|
||||
if (video && video.duration) {
|
||||
// $FlowFixMe
|
||||
let date = new Date(null);
|
||||
date.setSeconds(video.duration);
|
||||
let timeString = date.toISOString().substr(11, 8);
|
||||
|
||||
if (timeString.startsWith('00:')) {
|
||||
timeString = timeString.substr(3);
|
||||
}
|
||||
|
||||
duration = timeString;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page className="main--file-page">
|
||||
<div className="grid-area--content card">
|
||||
|
@ -295,7 +281,7 @@ class FilePage extends React.Component<Props> {
|
|||
</div>
|
||||
|
||||
<div className="media__subtext media__subtext--large">
|
||||
{video && <p className="media__info-text">{duration}</p>}
|
||||
<VideoDuration uri={uri} />
|
||||
|
||||
{claimIsMine && (
|
||||
<p>
|
||||
|
|
|
@ -18,9 +18,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Fix this in lbry/components
|
||||
.button--primary:not(:hover) {
|
||||
background-color: $lbry-teal-4;
|
||||
.button--primary {
|
||||
&:not(:hover) {
|
||||
// fix this in components repo
|
||||
background-color: $lbry-teal-4;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $lbry-teal-3;
|
||||
}
|
||||
|
||||
svg {
|
||||
color: white;
|
||||
}
|
||||
|
|
|
@ -96,8 +96,8 @@
|
|||
}
|
||||
|
||||
.claim-preview--injected,
|
||||
.claim-preview {
|
||||
border-top: 1px solid rgba($lbry-teal-5, 0.1);
|
||||
.claim-preview + .claim-preview {
|
||||
border-bottom: 1px solid rgba($lbry-teal-5, 0.1);
|
||||
}
|
||||
|
||||
.claim-preview--large {
|
||||
|
@ -147,6 +147,7 @@
|
|||
|
||||
.claim-preview-properties {
|
||||
align-items: flex-end;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.claim-preview-title {
|
||||
|
|
|
@ -1,25 +1,5 @@
|
|||
@import '~@lbry/components/sass/form/_index.scss';
|
||||
|
||||
// replace this
|
||||
form {
|
||||
// setting the font size here sizes everything within
|
||||
&:not(:last-child) {
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
height: var(--spacing-l);
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
checkbox-element,
|
||||
radio-element,
|
||||
select {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
textarea {
|
||||
&::placeholder {
|
||||
opacity: 0.4;
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
// S U B T I T L E
|
||||
|
||||
.media__subtitle {
|
||||
align-self: flex-start;
|
||||
font-size: 0.8em;
|
||||
color: rgba($lbry-black, 0.6);
|
||||
|
||||
|
|
|
@ -57,6 +57,10 @@ $main: $lbry-teal-5;
|
|||
&:active {
|
||||
background-color: $main;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@include focus;
|
||||
}
|
||||
}
|
||||
|
||||
.tag--remove {
|
||||
|
|
|
@ -15,3 +15,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin focus {
|
||||
box-shadow: 0 0 0 2px $lbry-blue-1;
|
||||
}
|
||||
|
|
|
@ -862,10 +862,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@lbry/color/-/color-1.1.1.tgz#b80ec25fb515d436129332b20c767c5a7014ac09"
|
||||
integrity sha512-BdxqWmm84WYOd1ZsPruJiGr7WBEophVfJKg7oywFOAMb0h6ERe4Idx1ceweMSvCOyPlR5GAhil9Gvk70SBFdxQ==
|
||||
|
||||
"@lbry/components@^2.7.2":
|
||||
version "2.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@lbry/components/-/components-2.7.2.tgz#a941ced2adf78ab52026f5533e5f6b3454f862a9"
|
||||
integrity sha512-TSBmxc4i2E3v7qGC7LgdcrUUcpiUYBA1KMVSW5vrpDJroNy3fnr3/niGJTudVQ4LQzlOXpt7bsRATFh8vRmXnQ==
|
||||
"@lbry/components@^2.7.4":
|
||||
version "2.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@lbry/components/-/components-2.7.4.tgz#3a633725137ffcb1f081f71dbf5397d66bf444cd"
|
||||
integrity sha512-X0QBpdlsPntmuC/COolx/rOmUxa5XeHDjdcpl79Db3Y3upkEYwwtvl+S/OkkPbdxouoJfZTB4QqQuGkNi1xGdw==
|
||||
|
||||
"@mapbox/hast-util-table-cell-style@^0.1.3":
|
||||
version "0.1.3"
|
||||
|
|
Loading…
Reference in a new issue