unified display of channel results #108

Merged
akinwale merged 5 commits from channel-results into master 2020-01-09 17:19:31 +01:00
11 changed files with 204 additions and 92 deletions

View file

@ -12,7 +12,7 @@ import {
} from 'lbry-redux'; } from 'lbry-redux';
import { selectBlackListedOutpoints, selectFilteredOutpoints, selectRewardContentClaimIds } from 'lbryinc'; import { selectBlackListedOutpoints, selectFilteredOutpoints, selectRewardContentClaimIds } from 'lbryinc';
import { selectShowNsfw } from 'redux/selectors/settings'; import { selectShowNsfw } from 'redux/selectors/settings';
import FileResultItem from './view'; import ClaimResultItem from './view';
const select = (state, props) => ({ const select = (state, props) => ({
blackListedOutpoints: selectBlackListedOutpoints(state), blackListedOutpoints: selectBlackListedOutpoints(state),
@ -37,4 +37,4 @@ const perform = dispatch => ({
export default connect( export default connect(
select, select,
perform, perform,
)(FileResultItem); )(ClaimResultItem);

View file

@ -1,10 +1,13 @@
import React from 'react'; import React from 'react';
import { normalizeURI, parseURI } from 'lbry-redux'; import { normalizeURI, parseURI } from 'lbry-redux';
import { ActivityIndicator, Platform, Text, TouchableOpacity, View } from 'react-native'; import { ActivityIndicator, Platform, Text, TouchableOpacity, View } from 'react-native';
import { navigateToUri, formatBytes } from 'utils/helper'; import { navigateToUri, formatTitle, getDownloadProgress, getStorageForFileInfo } from 'utils/helper';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import ChannelIconItem from 'component/channelIconItem';
import channelIconStyle from 'styles/channelIcon';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import DateTime from 'component/dateTime'; import DateTime from 'component/dateTime';
import FastImage from 'react-native-fast-image';
import FileItemMedia from 'component/fileItemMedia'; import FileItemMedia from 'component/fileItemMedia';
import FilePrice from 'component/filePrice'; import FilePrice from 'component/filePrice';
import Icon from 'react-native-vector-icons/FontAwesome5'; import Icon from 'react-native-vector-icons/FontAwesome5';
@ -12,30 +15,29 @@ import Link from 'component/link';
import NsfwOverlay from 'component/nsfwOverlay'; import NsfwOverlay from 'component/nsfwOverlay';
import ProgressBar from 'component/progressBar'; import ProgressBar from 'component/progressBar';
import fileListStyle from 'styles/fileList'; import fileListStyle from 'styles/fileList';
import seedrandom from 'seedrandom';
class FileResultItem extends React.PureComponent { class ClaimResultItem extends React.PureComponent {
getStorageForFileInfo = fileInfo => { state = {
if (!fileInfo.completed) { autoStyle: null,
const written = formatBytes(fileInfo.written_bytes); };
const total = formatBytes(fileInfo.total_bytes);
return `(${written} / ${total})`; componentDidMount() {
const { result } = this.props;
if (!result || !result.name || !result.claimId) {
this.setState({
autoStyle:
ChannelIconItem.AUTO_THUMB_STYLES[Math.floor(Math.random() * ChannelIconItem.AUTO_THUMB_STYLES.length)],
});
} else {
// result property set, use deterministic random style
const rng = seedrandom(normalizeURI(`${result.name}#${result.claimId}`));
const index = Math.floor(rng.quick() * ChannelIconItem.AUTO_THUMB_STYLES.length);
this.setState({ autoStyle: ChannelIconItem.AUTO_THUMB_STYLES[index] });
} }
return formatBytes(fileInfo.written_bytes);
};
formatTitle = title => {
if (!title) {
return title;
} }
return title.length > 80 ? title.substring(0, 77).trim() + '...' : title;
};
getDownloadProgress = fileInfo => {
return Math.ceil((fileInfo.written_bytes / fileInfo.total_bytes) * 100);
};
onPressHandler = () => { onPressHandler = () => {
const { autoplay, navigation, result } = this.props; const { autoplay, navigation, result } = this.props;
const { claimId, name } = result; const { claimId, name } = result;
@ -58,6 +60,8 @@ class FileResultItem extends React.PureComponent {
title, title,
} = result; } = result;
const isChannel = name && name.startsWith('@');
const hasThumbnail = !!thumbnailUrl;
const obscure = obscureNsfw && nsfw; const obscure = obscureNsfw && nsfw;
const url = normalizeURI(`${name}#${claimId}`); const url = normalizeURI(`${name}#${claimId}`);
const hasChannel = !!channel; const hasChannel = !!channel;
@ -66,7 +70,11 @@ class FileResultItem extends React.PureComponent {
return ( return (
<View style={style}> <View style={style}>
<TouchableOpacity style={style} onPress={this.onPressHandler}> <TouchableOpacity
style={[style, isChannel ? fileListStyle.channelContainer : null]}
onPress={this.onPressHandler}
>
{!isChannel && (
<FileItemMedia <FileItemMedia
style={fileListStyle.thumbnail} style={fileListStyle.thumbnail}
duration={duration} duration={duration}
@ -74,6 +82,27 @@ class FileResultItem extends React.PureComponent {
title={title || name || normalizeURI(url).substring(7)} title={title || name || normalizeURI(url).substring(7)}
thumbnail={thumbnailUrl} thumbnail={thumbnailUrl}
/> />
)}
{isChannel && (
<View style={fileListStyle.thumbnail}>
<View style={[fileListStyle.channelThumbnailContainer, this.state.autoStyle]}>
{hasThumbnail && (
<FastImage
style={fileListStyle.channelThumbnail}
resizeMode={FastImage.resizeMode.cover}
source={{ uri: thumbnailUrl }}
/>
)}
{!hasThumbnail && (
<Text style={channelIconStyle.autothumbCharacter}>
{title ? title.substring(0, 1).toUpperCase() : name.substring(1, 2).toUpperCase()}
</Text>
)}
</View>
</View>
)}
{fileInfo && fileInfo.completed && fileInfo.download_path && ( {fileInfo && fileInfo.completed && fileInfo.download_path && (
<Icon <Icon
style={featuredResult ? fileListStyle.featuredDownloadedIcon : fileListStyle.downloadedIcon} style={featuredResult ? fileListStyle.featuredDownloadedIcon : fileListStyle.downloadedIcon}
@ -94,25 +123,32 @@ class FileResultItem extends React.PureComponent {
{(title || name) && ( {(title || name) && (
<View style={fileListStyle.titleContainer}> <View style={fileListStyle.titleContainer}>
<Text style={featuredResult ? fileListStyle.featuredTitle : fileListStyle.title}> <Text style={featuredResult ? fileListStyle.featuredTitle : fileListStyle.title}>
{this.formatTitle(title) || this.formatTitle(name)} {formatTitle(title) || formatTitle(name)}
</Text> </Text>
{isRewardContent && <Icon style={fileListStyle.rewardIcon} name="award" size={12} />} {isRewardContent && <Icon style={fileListStyle.rewardIcon} name="award" size={12} />}
</View> </View>
)} )}
{hasChannel && ( {hasChannel ||
(isChannel && (
<Link <Link
style={fileListStyle.publisher} style={fileListStyle.publisher}
text={channel} text={isChannel ? name : channel}
onPress={() => { onPress={() => {
navigateToUri(navigation, normalizeURI(channelUrl), null, false, channelUrl); navigateToUri(
navigation,
normalizeURI(isChannel ? url : channelUrl),
null,
false,
isChannel ? url : channelUrl,
);
}} }}
/> />
)} ))}
<View style={fileListStyle.info}> <View style={fileListStyle.info}>
{fileInfo && !isNaN(fileInfo.written_bytes) && fileInfo.written_bytes > 0 && ( {fileInfo && !isNaN(fileInfo.written_bytes) && fileInfo.written_bytes > 0 && (
<Text>{this.getStorageForFileInfo(fileInfo)}</Text> <Text>{getStorageForFileInfo(fileInfo)}</Text>
)} )}
<DateTime <DateTime
style={fileListStyle.publishInfo} style={fileListStyle.publishInfo}
@ -130,7 +166,7 @@ class FileResultItem extends React.PureComponent {
color={Colors.NextLbryGreen} color={Colors.NextLbryGreen}
height={3} height={3}
style={fileListStyle.progress} style={fileListStyle.progress}
progress={this.getDownloadProgress(fileInfo)} progress={getDownloadProgress(fileInfo)}
/> />
)} )}
</View> </View>
@ -143,4 +179,4 @@ class FileResultItem extends React.PureComponent {
} }
} }
export default FileResultItem; export default ClaimResultItem;

View file

@ -1,10 +1,13 @@
import React from 'react'; import React from 'react';
import { normalizeURI, parseURI } from 'lbry-redux'; import { normalizeURI, parseURI } from 'lbry-redux';
import { ActivityIndicator, Platform, Text, TouchableOpacity, View } from 'react-native'; import { ActivityIndicator, Platform, Text, TouchableOpacity, View } from 'react-native';
import { navigateToUri, formatBytes } from 'utils/helper'; import { navigateToUri, formatTitle, getDownloadProgress, getStorageForFileInfo } from 'utils/helper';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import ChannelIconItem from 'component/channelIconItem';
import channelIconStyle from 'styles/channelIcon';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import DateTime from 'component/dateTime'; import DateTime from 'component/dateTime';
import FastImage from 'react-native-fast-image';
import FileItemMedia from 'component/fileItemMedia'; import FileItemMedia from 'component/fileItemMedia';
import FilePrice from 'component/filePrice'; import FilePrice from 'component/filePrice';
import Icon from 'react-native-vector-icons/FontAwesome5'; import Icon from 'react-native-vector-icons/FontAwesome5';
@ -15,27 +18,10 @@ import fileListStyle from 'styles/fileList';
class FileListItem extends React.PureComponent { class FileListItem extends React.PureComponent {
state = { state = {
autoStyle: null,
url: null, url: null,
}; };
getStorageForFileInfo = fileInfo => {
if (!fileInfo.completed) {
const written = formatBytes(fileInfo.written_bytes);
const total = formatBytes(fileInfo.total_bytes);
return `(${written} / ${total})`;
}
return formatBytes(fileInfo.written_bytes);
};
formatTitle = title => {
if (!title) {
return title;
}
return title.length > 80 ? title.substring(0, 77).trim() + '...' : title;
};
getDownloadProgress = fileInfo => { getDownloadProgress = fileInfo => {
return Math.ceil((fileInfo.written_bytes / fileInfo.total_bytes) * 100); return Math.ceil((fileInfo.written_bytes / fileInfo.total_bytes) * 100);
}; };
@ -45,6 +31,11 @@ class FileListItem extends React.PureComponent {
if (!claim && !batchResolve) { if (!claim && !batchResolve) {
resolveUri(uri); resolveUri(uri);
} }
this.setState({
autoStyle:
ChannelIconItem.AUTO_THUMB_STYLES[Math.floor(Math.random() * ChannelIconItem.AUTO_THUMB_STYLES.length)],
});
} }
componentDidUpdate() { componentDidUpdate() {
@ -135,10 +126,13 @@ class FileListItem extends React.PureComponent {
return null; return null;
} }
const isChannel = name && name.startsWith('@');
const hasThumbnail = !!thumbnail;
return ( return (
<View style={style}> <View style={style}>
<TouchableOpacity <TouchableOpacity
style={style} style={[style, isChannel ? fileListStyle.channelContainer : null]}
onPress={this.onPressHandler} onPress={this.onPressHandler}
onLongPress={() => { onLongPress={() => {
if (onLongPress) { if (onLongPress) {
@ -146,6 +140,7 @@ class FileListItem extends React.PureComponent {
} }
}} }}
> >
{!isChannel && (
<FileItemMedia <FileItemMedia
style={fileListStyle.thumbnail} style={fileListStyle.thumbnail}
duration={duration} duration={duration}
@ -153,6 +148,27 @@ class FileListItem extends React.PureComponent {
title={title || name || normalizeURI(uri).substring(7)} title={title || name || normalizeURI(uri).substring(7)}
thumbnail={thumbnail} thumbnail={thumbnail}
/> />
)}
{isChannel && (
<View style={fileListStyle.thumbnail}>
<View style={[fileListStyle.channelThumbnailContainer, this.state.autoStyle]}>
{hasThumbnail && (
<FastImage
style={fileListStyle.channelThumbnail}
resizeMode={FastImage.resizeMode.cover}
source={{ uri: thumbnail }}
/>
)}
{!hasThumbnail && (
<Text style={channelIconStyle.autothumbCharacter}>
{title ? title.substring(0, 1).toUpperCase() : claim.name.substring(1, 2).toUpperCase()}
</Text>
)}
</View>
</View>
)}
{selected && ( {selected && (
<View style={fileListStyle.selectedOverlay}> <View style={fileListStyle.selectedOverlay}>
<Icon name={'check-circle'} solid color={Colors.NextLbryGreen} size={32} /> <Icon name={'check-circle'} solid color={Colors.NextLbryGreen} size={32} />
@ -194,7 +210,7 @@ class FileListItem extends React.PureComponent {
{(title || name) && ( {(title || name) && (
<View style={fileListStyle.titleContainer}> <View style={fileListStyle.titleContainer}>
<Text style={featuredResult ? fileListStyle.featuredTitle : fileListStyle.title}> <Text style={featuredResult ? fileListStyle.featuredTitle : fileListStyle.title}>
{this.formatTitle(title) || this.formatTitle(name)} {formatTitle(title) || formatTitle(name)}
</Text> </Text>
{isRewardContent && <Icon style={fileListStyle.rewardIcon} name="award" size={12} />} {isRewardContent && <Icon style={fileListStyle.rewardIcon} name="award" size={12} />}
</View> </View>
@ -206,17 +222,17 @@ class FileListItem extends React.PureComponent {
</View> </View>
)} )}
{channel && !hideChannel && ( {(channel || isChannel) && !hideChannel && (
<Link <Link
style={fileListStyle.publisher} style={fileListStyle.publisher}
text={channel} text={isChannel ? name : channel}
onPress={() => { onPress={() => {
navigateToUri( navigateToUri(
navigation, navigation,
normalizeURI(shortChannelUri || fullChannelUri), normalizeURI(isChannel ? uri : shortChannelUri || fullChannelUri),
null, null,
false, false,
fullChannelUri, isChannel ? claim && claim.permanent_url : fullChannelUri,
); );
}} }}
/> />
@ -224,7 +240,7 @@ class FileListItem extends React.PureComponent {
<View style={fileListStyle.info}> <View style={fileListStyle.info}>
{fileInfo && !isNaN(fileInfo.written_bytes) && fileInfo.written_bytes > 0 && ( {fileInfo && !isNaN(fileInfo.written_bytes) && fileInfo.written_bytes > 0 && (
<Text style={fileListStyle.infoText}>{this.getStorageForFileInfo(fileInfo)}</Text> <Text style={fileListStyle.infoText}>{getStorageForFileInfo(fileInfo)}</Text>
)} )}
<DateTime style={fileListStyle.publishInfo} textStyle={fileListStyle.infoText} timeAgo uri={uri} /> <DateTime style={fileListStyle.publishInfo} textStyle={fileListStyle.infoText} timeAgo uri={uri} />
</View> </View>
@ -237,7 +253,7 @@ class FileListItem extends React.PureComponent {
color={Colors.NextLbryGreen} color={Colors.NextLbryGreen}
height={3} height={3}
style={fileListStyle.progress} style={fileListStyle.progress}
progress={this.getDownloadProgress(fileInfo)} progress={getDownloadProgress(fileInfo)}
/> />
)} )}
</View> </View>

View file

@ -4,7 +4,7 @@ import { normalizeURI } from 'lbry-redux';
import { navigateToUri } from 'utils/helper'; import { navigateToUri } from 'utils/helper';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import FileListItem from 'component/fileListItem'; import FileListItem from 'component/fileListItem';
import FileResultItem from 'component/fileResultItem'; import ClaimResultItem from 'component/claimResultItem';
import fileListStyle from 'styles/fileList'; import fileListStyle from 'styles/fileList';
import relatedContentStyle from 'styles/relatedContent'; import relatedContentStyle from 'styles/relatedContent';
@ -30,7 +30,7 @@ export default class RelatedContent extends React.PureComponent {
{isSearching && <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />} {isSearching && <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />}
{recommendedContent && {recommendedContent &&
recommendedContent.map(result => ( recommendedContent.map(result => (
<FileResultItem <ClaimResultItem
style={fileListStyle.item} style={fileListStyle.item}
key={result.claimId} key={result.claimId}
result={result} result={result}

View file

@ -3,6 +3,8 @@ import { buildURI, normalizeURI } from 'lbry-redux';
import { ActivityIndicator, FlatList, Image, Text, View } from 'react-native'; import { ActivityIndicator, FlatList, Image, Text, View } from 'react-native';
import { navigateToUri } from 'utils/helper'; import { navigateToUri } from 'utils/helper';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import ChannelIconItem from 'component/channelIconItem';
import channelIconStyle from 'styles/channelIcon';
import discoverStyle from 'styles/discover'; import discoverStyle from 'styles/discover';
import FileItem from 'component/fileItem'; import FileItem from 'component/fileItem';
import SubscribeButton from 'component/subscribeButton'; import SubscribeButton from 'component/subscribeButton';
@ -11,11 +13,20 @@ import Link from 'component/link';
import Tag from 'component/tag'; import Tag from 'component/tag';
class SuggestedSubscriptionItem extends React.PureComponent { class SuggestedSubscriptionItem extends React.PureComponent {
state = {
autoStyle: null,
};
componentDidMount() { componentDidMount() {
const { claim, uri, resolveUri } = this.props; const { claim, uri, resolveUri } = this.props;
if (!claim) { if (!claim) {
resolveUri(uri); resolveUri(uri);
} }
this.setState({
autoStyle:
ChannelIconItem.AUTO_THUMB_STYLES[Math.floor(Math.random() * ChannelIconItem.AUTO_THUMB_STYLES.length)],
});
} }
render() { render() {
@ -28,6 +39,7 @@ class SuggestedSubscriptionItem extends React.PureComponent {
} }
} }
const hasThumbnail = !!thumbnail;
if (isResolvingUri) { if (isResolvingUri) {
return ( return (
<View style={subscriptionsStyle.itemLoadingContainer}> <View style={subscriptionsStyle.itemLoadingContainer}>
@ -38,12 +50,15 @@ class SuggestedSubscriptionItem extends React.PureComponent {
return ( return (
<View style={subscriptionsStyle.suggestedItem}> <View style={subscriptionsStyle.suggestedItem}>
<View style={subscriptionsStyle.suggestedItemThumbnailContainer}> <View style={[subscriptionsStyle.suggestedItemThumbnailContainer, this.state.autoStyle]}>
<Image {hasThumbnail && (
style={subscriptionsStyle.suggestedItemThumbnail} <Image style={subscriptionsStyle.suggestedItemThumbnail} resizeMode={'cover'} source={{ uri: thumbnail }} />
resizeMode={'cover'} )}
source={thumbnail ? { uri: thumbnail } : require('../../assets/default_avatar.jpg')} {!hasThumbnail && (
/> <Text style={channelIconStyle.autothumbCharacter}>
{title ? title.substring(0, 1).toUpperCase() : claim ? claim.name.substring(1, 2).toUpperCase() : ''}
</Text>
)}
</View> </View>
<View style={subscriptionsStyle.suggestedItemDetails}> <View style={subscriptionsStyle.suggestedItemDetails}>

View file

@ -21,6 +21,7 @@ import ChannelRewardsDriver from 'component/channelRewardsDriver';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import EmptyStateView from 'component/emptyStateView'; import EmptyStateView from 'component/emptyStateView';
import FastImage from 'react-native-fast-image';
import FloatingWalletBalance from 'component/floatingWalletBalance'; import FloatingWalletBalance from 'component/floatingWalletBalance';
import Icon from 'react-native-vector-icons/FontAwesome5'; import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link'; import Link from 'component/link';
@ -867,9 +868,9 @@ export default class ChannelCreator extends React.PureComponent {
> >
<View style={[channelCreatorStyle.channelListAvatar, itemAutoStyle]}> <View style={[channelCreatorStyle.channelListAvatar, itemAutoStyle]}>
{itemThumbnailUrl && ( {itemThumbnailUrl && (
<Image <FastImage
style={channelCreatorStyle.avatarImage} style={channelCreatorStyle.avatarImage}
resizeMode={'cover'} resizeMode={FastImage.resizeMode.cover}
source={{ uri: itemThumbnailUrl }} source={{ uri: itemThumbnailUrl }}
/> />
)} )}

View file

@ -15,7 +15,7 @@ import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import PageHeader from 'component/pageHeader'; import PageHeader from 'component/pageHeader';
import FileListItem from 'component/fileListItem'; import FileListItem from 'component/fileListItem';
import FileResultItem from 'component/fileResultItem'; import ClaimResultItem from 'component/claimResultItem';
import FloatingWalletBalance from 'component/floatingWalletBalance'; import FloatingWalletBalance from 'component/floatingWalletBalance';
import UriBar from 'component/uriBar'; import UriBar from 'component/uriBar';
import searchStyle from 'styles/search'; import searchStyle from 'styles/search';
@ -213,7 +213,12 @@ class SearchPage extends React.PureComponent {
ListEmptyComponent={!isSearching ? this.listEmptyComponent() : null} ListEmptyComponent={!isSearching ? this.listEmptyComponent() : null}
ListHeaderComponent={this.listHeaderComponent(this.state.showTagResult, this.state.currentQuery)} ListHeaderComponent={this.listHeaderComponent(this.state.showTagResult, this.state.currentQuery)}
renderItem={({ item }) => ( renderItem={({ item }) => (
<FileResultItem key={item.claimId} result={item} style={searchStyle.resultItem} navigation={navigation} /> <ClaimResultItem
key={item.claimId}
result={item}
style={searchStyle.resultItem}
navigation={navigation}
/>
)} )}
/> />
)} )}

View file

@ -73,7 +73,7 @@ class SubscriptionsPage extends React.PureComponent {
}; };
componentDidMount() { componentDidMount() {
// this.onComponentFocused(); this.onComponentFocused();
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {

View file

@ -38,6 +38,18 @@ const fileListStyle = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
}, },
channelThumbnailContainer: {
width: thumbnailHeight, // maintain same width and height
height: thumbnailHeight,
borderRadius: 140,
overflow: 'hidden',
alignItems: 'center',
justifyContent: 'center',
},
channelThumbnail: {
width: '100%',
height: '100%',
},
selectedOverlay: { selectedOverlay: {
position: 'absolute', position: 'absolute',
left: 0, left: 0,
@ -63,6 +75,9 @@ const fileListStyle = StyleSheet.create({
marginTop: screenWidthPixels <= 720 ? 1 : 3, marginTop: screenWidthPixels <= 720 ? 1 : 3,
color: Colors.LbryGreen, color: Colors.LbryGreen,
}, },
channelContainer: {
alignItems: 'center',
},
loading: { loading: {
position: 'absolute', position: 'absolute',
}, },

View file

@ -199,6 +199,8 @@ const subscriptionsStyle = StyleSheet.create({
height: 70, height: 70,
borderRadius: 140, borderRadius: 140,
overflow: 'hidden', overflow: 'hidden',
alignItems: 'center',
justifyContent: 'center',
}, },
suggestedItemThumbnail: { suggestedItemThumbnail: {
width: '100%', width: '100%',

View file

@ -370,3 +370,25 @@ export function uploadImageAsset(filePath, success, failure) {
export function formatLbryUrlForWeb(url) { export function formatLbryUrlForWeb(url) {
return url.replace('lbry://', '/').replace(/#/g, ':'); return url.replace('lbry://', '/').replace(/#/g, ':');
} }
export function getDownloadProgress(fileInfo) {
return Math.ceil((fileInfo.written_bytes / fileInfo.total_bytes) * 100);
}
export function getStorageForFileInfo(fileInfo) {
if (!fileInfo.completed) {
const written = formatBytes(fileInfo.written_bytes);
const total = formatBytes(fileInfo.total_bytes);
return `(${written} / ${total})`;
}
return formatBytes(fileInfo.written_bytes);
}
export function formatTitle(title) {
if (!title) {
return title;
}
return title.length > 80 ? title.substring(0, 77).trim() + '...' : title;
}