refactor tooltip
This commit is contained in:
parent
19c8e55d08
commit
f10530bd5d
14 changed files with 378 additions and 205 deletions
|
@ -63,6 +63,7 @@
|
||||||
"@lbry/components": "^2.7.0",
|
"@lbry/components": "^2.7.0",
|
||||||
"@reach/rect": "^0.2.1",
|
"@reach/rect": "^0.2.1",
|
||||||
"@reach/tabs": "^0.1.5",
|
"@reach/tabs": "^0.1.5",
|
||||||
|
"@reach/tooltip": "^0.2.1",
|
||||||
"@types/three": "^0.93.1",
|
"@types/three": "^0.93.1",
|
||||||
"async-exit-hook": "^2.0.1",
|
"async-exit-hook": "^2.0.1",
|
||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.1",
|
||||||
|
@ -148,7 +149,6 @@
|
||||||
"react-hot-loader": "^4.7.2",
|
"react-hot-loader": "^4.7.2",
|
||||||
"react-modal": "^3.1.7",
|
"react-modal": "^3.1.7",
|
||||||
"react-paginate": "^5.2.1",
|
"react-paginate": "^5.2.1",
|
||||||
"react-portal-tooltip": "^2.4.7",
|
|
||||||
"react-pose": "^4.0.5",
|
"react-pose": "^4.0.5",
|
||||||
"react-redux": "^6.0.1",
|
"react-redux": "^6.0.1",
|
||||||
"react-router": "^5.0.0",
|
"react-router": "^5.0.0",
|
||||||
|
@ -175,7 +175,6 @@
|
||||||
"three": "^0.93.0",
|
"three": "^0.93.0",
|
||||||
"three-full": "^17.1.0",
|
"three-full": "^17.1.0",
|
||||||
"tree-kill": "^1.1.0",
|
"tree-kill": "^1.1.0",
|
||||||
"uniqid": "^5.0.3",
|
|
||||||
"unist-util-visit": "^1.4.1",
|
"unist-util-visit": "^1.4.1",
|
||||||
"video.js": "^7.2.2",
|
"video.js": "^7.2.2",
|
||||||
"villain": "btzr-io/Villain",
|
"villain": "btzr-io/Villain",
|
||||||
|
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
12
src/ui/component/channelThumbnail/index.js
Normal file
12
src/ui/component/channelThumbnail/index.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { makeSelectThumbnailForUri } from 'lbry-redux';
|
||||||
|
import ChannelThumbnail from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
null
|
||||||
|
)(ChannelThumbnail);
|
|
@ -5,22 +5,23 @@ import classnames from 'classnames';
|
||||||
import Gerbil from './gerbil.png';
|
import Gerbil from './gerbil.png';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
|
||||||
thumbnail: ?string,
|
thumbnail: ?string,
|
||||||
|
uri: string,
|
||||||
|
className?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ChannelThumbnail(props: Props) {
|
function ChannelThumbnail(props: Props) {
|
||||||
const { thumbnail, uri } = props;
|
const { thumbnail, uri, className } = props;
|
||||||
|
|
||||||
// Generate a random color class based on the first letter of the channel name
|
// Generate a random color class based on the first letter of the channel name
|
||||||
const { channelName } = parseURI(uri);
|
const { channelName } = parseURI(uri);
|
||||||
const initializer = channelName.charCodeAt(0) - 65; // will be between 0 and 57
|
const initializer = channelName.charCodeAt(0) - 65; // will be between 0 and 57
|
||||||
const thumbnailClass = `channel-thumbnail__default--${initializer % 4}`;
|
const colorClassName = `channel-thumbnail__default--${initializer % 4}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classnames('channel-thumbnail', {
|
className={classnames('channel-thumbnail', className, {
|
||||||
[thumbnailClass]: !thumbnail,
|
[colorClassName]: !thumbnail,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{!thumbnail && <img className="channel-thumbnail__default" src={Gerbil} />}
|
{!thumbnail && <img className="channel-thumbnail__default" src={Gerbil} />}
|
2
src/ui/component/channelTooltip/index.js
Normal file
2
src/ui/component/channelTooltip/index.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import ChannelTooltip from './view';
|
||||||
|
export default ChannelTooltip;
|
41
src/ui/component/channelTooltip/position.js
Normal file
41
src/ui/component/channelTooltip/position.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// Defualt function used by reach-ui tooltip componment,
|
||||||
|
// slightly modify to register current direction:
|
||||||
|
// https://github.com/reach/reach-ui/blob/f0c8c5c467be46c202148ee69b1ba789b57d5e60/packages/tooltip/src/index.js#L478
|
||||||
|
|
||||||
|
// feels awkward when it's perfectly aligned w/ the trigger
|
||||||
|
const OFFSET = 8;
|
||||||
|
|
||||||
|
const positionDefault = (triggerRect, tooltipRect, currentDirection, setDirection) => {
|
||||||
|
const styles = {
|
||||||
|
left: `${triggerRect.left + window.pageXOffset}px`,
|
||||||
|
top: `${triggerRect.top + triggerRect.height + window.pageYOffset}px`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const collisions = {
|
||||||
|
top: triggerRect.top - tooltipRect.height < 0,
|
||||||
|
right: window.innerWidth < triggerRect.left + tooltipRect.width,
|
||||||
|
bottom: window.innerHeight < triggerRect.bottom + tooltipRect.height + OFFSET,
|
||||||
|
left: triggerRect.left - tooltipRect.width < 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const directionRight = collisions.right && !collisions.left;
|
||||||
|
const directionUp = collisions.bottom && !collisions.top;
|
||||||
|
|
||||||
|
const direction = directionUp ? 'top' : 'bottom';
|
||||||
|
|
||||||
|
if (direction !== currentDirection) {
|
||||||
|
setDirection(direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...styles,
|
||||||
|
left: directionRight
|
||||||
|
? `${triggerRect.right - tooltipRect.width + window.pageXOffset}px`
|
||||||
|
: `${triggerRect.left + window.pageXOffset}px`,
|
||||||
|
top: directionUp
|
||||||
|
? `${triggerRect.top - OFFSET - tooltipRect.height + window.pageYOffset}px`
|
||||||
|
: `${triggerRect.top + OFFSET + triggerRect.height + window.pageYOffset}px`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default positionDefault;
|
130
src/ui/component/channelTooltip/view.jsx
Normal file
130
src/ui/component/channelTooltip/view.jsx
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import ChannelThumbnail from 'component/channelThumbnail';
|
||||||
|
import TruncatedText from 'component/common/truncated-text';
|
||||||
|
|
||||||
|
// Tooltip components
|
||||||
|
import Portal from '@reach/portal';
|
||||||
|
import getPostion from './position.js';
|
||||||
|
import { useTooltip, TooltipPopup } from '@reach/tooltip';
|
||||||
|
|
||||||
|
// Tooltip base styles
|
||||||
|
import '@reach/tooltip/styles.css';
|
||||||
|
|
||||||
|
const ARROW_SIZE = 10;
|
||||||
|
|
||||||
|
type ChannelTooltipProps = {
|
||||||
|
uri: string,
|
||||||
|
title: ?string,
|
||||||
|
claimId: string,
|
||||||
|
children: any,
|
||||||
|
ariaLabel: ?string,
|
||||||
|
thumbnail: ?string,
|
||||||
|
channelName: string,
|
||||||
|
description: ?string,
|
||||||
|
};
|
||||||
|
|
||||||
|
type TriangleTooltipProps = {
|
||||||
|
label: any,
|
||||||
|
children: any,
|
||||||
|
|
||||||
|
ariaLabel: ?string,
|
||||||
|
};
|
||||||
|
|
||||||
|
function TriangleTooltip(props: TriangleTooltipProps) {
|
||||||
|
const { label, children, ariaLabel } = props;
|
||||||
|
|
||||||
|
// get the props from useTooltip
|
||||||
|
const [trigger, tooltip] = useTooltip();
|
||||||
|
|
||||||
|
// Tooltip direction
|
||||||
|
const [direction, setDirection] = React.useState('bottom');
|
||||||
|
|
||||||
|
// destructure off what we need to position the triangle
|
||||||
|
const { isVisible, triggerRect } = tooltip;
|
||||||
|
|
||||||
|
let arrowTop;
|
||||||
|
|
||||||
|
// Top
|
||||||
|
if (direction === 'top') {
|
||||||
|
arrowTop = triggerRect && triggerRect.bottom - (triggerRect.height + ARROW_SIZE) + window.scrollY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom
|
||||||
|
if (direction === 'bottom') {
|
||||||
|
arrowTop = triggerRect && triggerRect.bottom + window.scrollY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{React.cloneElement(children, trigger)}
|
||||||
|
{isVisible && (
|
||||||
|
// The Triangle. We position it relative to the trigger, not the popup
|
||||||
|
// so that collisions don't have a triangle pointing off to nowhere.
|
||||||
|
// Using a Portal may seem a little extreme, but we can keep the
|
||||||
|
// positioning logic simpler here instead of needing to consider
|
||||||
|
// the popup's position relative to the trigger and collisions
|
||||||
|
<Portal>
|
||||||
|
<span
|
||||||
|
className={classnames('channel-tooltip__arrow', `channel-tooltip__arrow--${direction}`)}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: arrowTop,
|
||||||
|
left: triggerRect && triggerRect.left - ARROW_SIZE + triggerRect.width / 2,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Portal>
|
||||||
|
)}
|
||||||
|
<TooltipPopup
|
||||||
|
{...tooltip}
|
||||||
|
label={label}
|
||||||
|
ariaLabel={ariaLabel}
|
||||||
|
className={'channel-tooltip'}
|
||||||
|
position={(triggerRect, tooltipRect) => getPostion(triggerRect, tooltipRect, direction, setDirection)}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChannelTooltipLabel = (props: ChannelTooltipProps) => {
|
||||||
|
const { uri, title, claimId, thumbnail, description, channelName } = props;
|
||||||
|
const channelUrl = `${channelName}#${claimId}`;
|
||||||
|
const formatedName = channelName.replace('@', '');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'channel-tooltip__content'}>
|
||||||
|
<div className={'channel-tooltip__profile'}>
|
||||||
|
<ChannelThumbnail uri={uri} thumbnail={thumbnail} />
|
||||||
|
<div className={'channel-tooltip__info'}>
|
||||||
|
<h2 className={'channel-tooltip__title'}>
|
||||||
|
<TruncatedText lines={1}>{title || formatedName}</TruncatedText>
|
||||||
|
</h2>
|
||||||
|
<h3 className={'channel-tooltip__url'}>
|
||||||
|
<TruncatedText lines={1}>{channelUrl}</TruncatedText>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{description && (
|
||||||
|
<div className={'channel-tooltip__description'}>
|
||||||
|
<div>
|
||||||
|
<TruncatedText lines={2}>{description}</TruncatedText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={'channel-tooltip__stats'} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ChannelTooltip = (props: ChannelTooltipProps) => {
|
||||||
|
const { children, ariaLabel } = props;
|
||||||
|
const label = <ChannelTooltipLabel {...props} />;
|
||||||
|
return (
|
||||||
|
<TriangleTooltip label={label} ariaLabel={ariaLabel}>
|
||||||
|
{children}
|
||||||
|
</TriangleTooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo<ChannelTooltipProps>(ChannelTooltip);
|
|
@ -1,10 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
import uniqid from 'uniqid';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { parseURI } from 'lbry-redux';
|
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import PreviewLink from 'component/previewLink';
|
import PreviewLink from 'component/previewLink';
|
||||||
import ChannelTooltip from 'component/common/channel-tooltip';
|
import ChannelTooltip from 'component/channelTooltip';
|
||||||
|
import { parseURI } from 'lbry-redux';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
|
@ -23,13 +22,7 @@ type Props = {
|
||||||
}>,
|
}>,
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
class ClaimLink extends React.Component<Props> {
|
||||||
isTooltipActive: boolean,
|
|
||||||
};
|
|
||||||
|
|
||||||
class ClaimLink extends React.Component<Props, State> {
|
|
||||||
parentId: string;
|
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
href: null,
|
href: null,
|
||||||
title: null,
|
title: null,
|
||||||
|
@ -39,24 +32,6 @@ class ClaimLink extends React.Component<Props, State> {
|
||||||
isResolvingUri: false,
|
isResolvingUri: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
this.state = { isTooltipActive: false };
|
|
||||||
|
|
||||||
// The tooltip component don't work very well with refs,
|
|
||||||
// so we need to use an unique id for each link:
|
|
||||||
// (this: any).buttonRef = React.createRef();
|
|
||||||
(this: any).parentId = uniqid.time('claim-link-');
|
|
||||||
}
|
|
||||||
|
|
||||||
showTooltip = () => {
|
|
||||||
this.setState({ isTooltipActive: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
hideTooltip = () => {
|
|
||||||
this.setState({ isTooltipActive: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
isClaimBlackListed() {
|
isClaimBlackListed() {
|
||||||
const { claim, blackListedOutpoints } = this.props;
|
const { claim, blackListedOutpoints } = this.props;
|
||||||
|
|
||||||
|
@ -97,38 +72,40 @@ class ClaimLink extends React.Component<Props, State> {
|
||||||
return <span>{children}</span>;
|
return <span>{children}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isChannel, path } = parseURI(uri);
|
|
||||||
const { claim_id: claimId, name: claimName } = claim;
|
const { claim_id: claimId, name: claimName } = claim;
|
||||||
|
const { isChannel, path } = parseURI(uri);
|
||||||
|
const isChannelClaim = isChannel && !path;
|
||||||
const showPreview = autoEmbed === true && !isUnresolved;
|
const showPreview = autoEmbed === true && !isUnresolved;
|
||||||
const renderChannelTooltip = isChannel && !path;
|
|
||||||
|
|
||||||
return (
|
const link = (
|
||||||
<React.Fragment>
|
|
||||||
<Button
|
<Button
|
||||||
id={this.parentId}
|
|
||||||
label={children}
|
label={children}
|
||||||
title={title || claimName}
|
title={!isChannelClaim ? title || claimName : undefined}
|
||||||
button={'link'}
|
button={!isChannel ? 'link' : undefined}
|
||||||
navigate={uri}
|
navigate={uri}
|
||||||
className="button--uri-indicator"
|
className="button--uri-indicator"
|
||||||
onMouseEnter={this.showTooltip}
|
|
||||||
onMouseLeave={this.hideTooltip}
|
|
||||||
/>
|
/>
|
||||||
{showPreview && <PreviewLink uri={uri} />}
|
);
|
||||||
{renderChannelTooltip && (
|
|
||||||
|
const wrappedLink = (
|
||||||
<ChannelTooltip
|
<ChannelTooltip
|
||||||
uri={uri}
|
uri={uri}
|
||||||
title={title}
|
title={title}
|
||||||
claimId={claimId}
|
claimId={claimId}
|
||||||
channelName={claimName}
|
channelName={claimName}
|
||||||
|
ariaLabel={title || claimName}
|
||||||
currentTheme={currentTheme}
|
currentTheme={currentTheme}
|
||||||
thumbnail={thumbnail}
|
thumbnail={thumbnail}
|
||||||
description={description}
|
description={description}
|
||||||
active={this.state.isTooltipActive}
|
>
|
||||||
parent={`#${this.parentId}`}
|
<span>{link}</span>
|
||||||
group={'channel-tooltip'}
|
</ChannelTooltip>
|
||||||
/>
|
);
|
||||||
)}
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{isChannelClaim ? wrappedLink : link}
|
||||||
|
{showPreview && <PreviewLink uri={uri} />}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as React from 'react';
|
|
||||||
import ToolTip from 'react-portal-tooltip';
|
|
||||||
import TruncatedText from 'component/common/truncated-text';
|
|
||||||
import ChannelThumbnail from 'component/common/channelThumbnail';
|
|
||||||
|
|
||||||
type TooltipProps = {
|
|
||||||
uri: string,
|
|
||||||
title: ?string,
|
|
||||||
group: ?string,
|
|
||||||
active: ?boolean,
|
|
||||||
parent: ?HTMLElement | ?string,
|
|
||||||
claimId: string,
|
|
||||||
thumbnail: ?string,
|
|
||||||
channelName: string,
|
|
||||||
description: ?string,
|
|
||||||
currentTheme: ?string,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ChannelTooltip = (props: TooltipProps) => {
|
|
||||||
const { uri, group, title, active, parent, claimId, thumbnail, description, channelName, currentTheme } = props;
|
|
||||||
|
|
||||||
let bgColor = 'var(--lbry-white)';
|
|
||||||
|
|
||||||
if (currentTheme === 'dark') {
|
|
||||||
// Background color of the tooltip component,
|
|
||||||
// taken from the header component:
|
|
||||||
// mix($lbry-black, $lbry-gray-3, 90%);
|
|
||||||
bgColor = '#32373b';
|
|
||||||
}
|
|
||||||
|
|
||||||
const style = {
|
|
||||||
style: { background: bgColor, padding: 0 },
|
|
||||||
arrowStyle: { color: bgColor, borderColor: false },
|
|
||||||
};
|
|
||||||
|
|
||||||
const channelUrl = `${channelName}#${claimId}`;
|
|
||||||
const formatedName = channelName.replace('@', '');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ToolTip
|
|
||||||
align="left"
|
|
||||||
arrow="left"
|
|
||||||
active={parent && active}
|
|
||||||
group={group}
|
|
||||||
style={style}
|
|
||||||
parent={parent}
|
|
||||||
position="bottom"
|
|
||||||
>
|
|
||||||
<div className={'channel-tooltip'}>
|
|
||||||
<div className={'channel-tooltip__profile'}>
|
|
||||||
<ChannelThumbnail uri={uri} thumbnail={thumbnail} />
|
|
||||||
<div className={'channel-tooltip__info'}>
|
|
||||||
<h2 className={'channel-tooltip__title'}>
|
|
||||||
<TruncatedText lines={1}>{title || formatedName}</TruncatedText>
|
|
||||||
</h2>
|
|
||||||
<h3 className={'channel-tooltip__url'}>
|
|
||||||
<TruncatedText lines={1}>{channelUrl}</TruncatedText>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{description && (
|
|
||||||
<div className={'channel-tooltip__description'}>
|
|
||||||
<p>
|
|
||||||
<TruncatedText lines={2}>{description}</TruncatedText>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={'channel-tooltip__stats'} />
|
|
||||||
</div>
|
|
||||||
</ToolTip>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default React.memo<TooltipProps>(ChannelTooltip);
|
|
|
@ -17,18 +17,20 @@ type Props = {
|
||||||
|
|
||||||
class UriIndicator extends React.PureComponent<Props> {
|
class UriIndicator extends React.PureComponent<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { isResolvingUri, resolveUri, uri, claim } = this.props;
|
this.resolve(this.props);
|
||||||
if (!isResolvingUri && !claim && uri) {
|
|
||||||
resolveUri(uri);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Props) {
|
componentDidUpdate() {
|
||||||
const { isResolvingUri, resolveUri, claim, uri } = this.props;
|
this.resolve(this.props);
|
||||||
if (!isResolvingUri && uri && !claim) {
|
}
|
||||||
|
|
||||||
|
resolve = (props: Props) => {
|
||||||
|
const { isResolvingUri, resolveUri, claim, uri } = props;
|
||||||
|
|
||||||
|
if (!isResolvingUri && claim === undefined && uri) {
|
||||||
resolveUri(uri);
|
resolveUri(uri);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { link, isResolvingUri, claim } = this.props;
|
const { link, isResolvingUri, claim } = this.props;
|
||||||
|
@ -37,23 +39,31 @@ class UriIndicator extends React.PureComponent<Props> {
|
||||||
return <span className="empty">{isResolvingUri ? 'Validating...' : 'Unused'}</span>;
|
return <span className="empty">{isResolvingUri ? 'Validating...' : 'Unused'}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!claim.signing_channel) {
|
const isChannelClaim = claim.value_type === 'channel';
|
||||||
|
|
||||||
|
if (!claim.signing_channel && !isChannelClaim) {
|
||||||
return <span className="channel-name">Anonymous</span>;
|
return <span className="channel-name">Anonymous</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, claim_id: claimId } = claim.signing_channel;
|
const channelClaim = isChannelClaim ? claim : claim.signing_channel;
|
||||||
let claimLink;
|
|
||||||
|
if (channelClaim) {
|
||||||
|
const { name, claim_id: claimId } = channelClaim;
|
||||||
|
let channelLink;
|
||||||
if (claim.is_channel_signature_valid) {
|
if (claim.is_channel_signature_valid) {
|
||||||
claimLink = link ? buildURI({ channelName: name, claimId }) : false;
|
channelLink = link ? buildURI({ channelName: name, claimId }) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inner = <span className="channel-name">{name}</span>;
|
const inner = <span className="channel-name">{name}</span>;
|
||||||
|
|
||||||
if (!claimLink) {
|
if (!channelLink) {
|
||||||
return inner;
|
return inner;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ClaimLink uri={claimLink}>{inner}</ClaimLink>;
|
return <ClaimLink uri={channelLink}>{inner}</ClaimLink>;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,11 @@ import {
|
||||||
import ChannelPage from './view';
|
import ChannelPage from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
page: selectCurrentChannelPage(state),
|
|
||||||
cover: makeSelectCoverForUri(props.uri)(state),
|
|
||||||
title: makeSelectTitleForUri(props.uri)(state),
|
title: makeSelectTitleForUri(props.uri)(state),
|
||||||
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
||||||
|
cover: makeSelectCoverForUri(props.uri)(state),
|
||||||
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
|
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
|
page: selectCurrentChannelPage(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { withRouter } from 'react-router';
|
||||||
import { formatLbryUriForWeb } from 'util/uri';
|
import { formatLbryUriForWeb } from 'util/uri';
|
||||||
import ChannelContent from 'component/channelContent';
|
import ChannelContent from 'component/channelContent';
|
||||||
import ChannelAbout from 'component/channelAbout';
|
import ChannelAbout from 'component/channelAbout';
|
||||||
import ChannelThumbnail from 'component/common/channelThumbnail';
|
import ChannelThumbnail from 'component/channelThumbnail';
|
||||||
|
|
||||||
const PAGE_VIEW_QUERY = `view`;
|
const PAGE_VIEW_QUERY = `view`;
|
||||||
const ABOUT_PAGE = `about`;
|
const ABOUT_PAGE = `about`;
|
||||||
|
@ -26,7 +26,7 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function ChannelPage(props: Props) {
|
function ChannelPage(props: Props) {
|
||||||
const { uri, title, cover, thumbnail, history, location, page } = props;
|
const { uri, title, cover, history, location, page } = props;
|
||||||
const { channelName, claimName, claimId } = parseURI(uri);
|
const { channelName, claimName, claimId } = parseURI(uri);
|
||||||
const { search } = location;
|
const { search } = location;
|
||||||
const urlParams = new URLSearchParams(search);
|
const urlParams = new URLSearchParams(search);
|
||||||
|
@ -49,12 +49,13 @@ function ChannelPage(props: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page notContained className="main--no-padding-top">
|
<Page>
|
||||||
<header className="channel-cover main__item--extend-outside">
|
<div className="card">
|
||||||
|
<header className="channel-cover">
|
||||||
{cover && <img className="channel-cover__custom" src={cover} />}
|
{cover && <img className="channel-cover__custom" src={cover} />}
|
||||||
|
|
||||||
<div className="channel__primary-info">
|
<div className="channel__primary-info">
|
||||||
<ChannelThumbnail uri={uri} thumbnail={thumbnail} />
|
<ChannelThumbnail className="channel__thumbnail--channel-page" uri={uri} />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h1 className="channel__title">{title || channelName}</h1>
|
<h1 className="channel__title">{title || channelName}</h1>
|
||||||
|
@ -67,7 +68,7 @@ function ChannelPage(props: Props) {
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<Tabs onChange={onTabChange} index={tabIndex}>
|
<Tabs onChange={onTabChange} index={tabIndex}>
|
||||||
<TabList className="main__item--extend-outside tabs__list--channel-page">
|
<TabList className="tabs__list--channel-page">
|
||||||
<Tab>{__('Content')}</Tab>
|
<Tab>{__('Content')}</Tab>
|
||||||
<Tab>{__('About')}</Tab>
|
<Tab>{__('About')}</Tab>
|
||||||
<div className="card__actions">
|
<div className="card__actions">
|
||||||
|
@ -76,7 +77,7 @@ function ChannelPage(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
</TabList>
|
</TabList>
|
||||||
|
|
||||||
<TabPanels>
|
<TabPanels className="channel__data">
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<ChannelContent uri={uri} />
|
<ChannelContent uri={uri} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
@ -85,6 +86,7 @@ function ChannelPage(props: Props) {
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,7 +154,28 @@
|
||||||
// Channel-tooltip
|
// Channel-tooltip
|
||||||
|
|
||||||
.channel-tooltip {
|
.channel-tooltip {
|
||||||
|
color: $lbry-black;
|
||||||
|
background-color: $lbry-white;
|
||||||
width: 20rem;
|
width: 20rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--card-radius);
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
html[data-mode='dark'] & {
|
||||||
|
color: mix($lbry-white, $lbry-gray-3, 50%);
|
||||||
|
background-color: mix($lbry-black, $lbry-gray-3, 90%);
|
||||||
|
}
|
||||||
|
|
||||||
|
box-shadow: 0px 2px 10px -2px black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-tooltip__content {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
display: block;
|
||||||
|
word-break: break-all;
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
|
||||||
.channel-thumbnail {
|
.channel-thumbnail {
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -169,6 +190,39 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.channel-tooltip__arrow {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 10px solid transparent;
|
||||||
|
border-right: 10px solid transparent;
|
||||||
|
|
||||||
|
html[data-mode='dark'] & {
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-tooltip__arrow--top {
|
||||||
|
border-top: 10px solid $lbry-gray-5;
|
||||||
|
border-bottom: 0;
|
||||||
|
|
||||||
|
html[data-mode='dark'] & {
|
||||||
|
z-index: 3;
|
||||||
|
border-top: 10px solid mix($lbry-black, $lbry-gray-3, 90%);
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-tooltip__arrow--bottom {
|
||||||
|
border-top: 0;
|
||||||
|
border-bottom: 10px solid $lbry-gray-5;
|
||||||
|
|
||||||
|
html[data-mode='dark'] & {
|
||||||
|
z-index: 3;
|
||||||
|
border-top: 0;
|
||||||
|
border-bottom: 10px solid mix($lbry-black, $lbry-gray-3, 90%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.channel-tooltip__profile {
|
.channel-tooltip__profile {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -179,6 +233,7 @@
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
border-top: 1px solid rgba($lbry-gray-4, 0.4);
|
border-top: 1px solid rgba($lbry-gray-4, 0.4);
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
html[data-mode='dark'] & {
|
html[data-mode='dark'] & {
|
||||||
border-top: 1px solid rgba($lbry-black, 0.8);
|
border-top: 1px solid rgba($lbry-black, 0.8);
|
||||||
|
@ -186,16 +241,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-tooltip__info {
|
.channel-tooltip__info {
|
||||||
align-items: center;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-tooltip__title {
|
.channel-tooltip__title {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-tooltip__url {
|
.channel-tooltip__url {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
41
yarn.lock
41
yarn.lock
|
@ -893,7 +893,7 @@
|
||||||
resolved "https://registry.yarnpkg.com/@posthtml/esm/-/esm-1.0.0.tgz#09bcb28a02438dcee22ad1970ca1d85a000ae0cf"
|
resolved "https://registry.yarnpkg.com/@posthtml/esm/-/esm-1.0.0.tgz#09bcb28a02438dcee22ad1970ca1d85a000ae0cf"
|
||||||
integrity sha512-dEVG+ITnvqKGa4v040tP+n8LOKOqr94qjLva7bE5pnfm2KHJwsKz69J4KMxgWLznbpBJzy8vQfCayEk3vLZnZQ==
|
integrity sha512-dEVG+ITnvqKGa4v040tP+n8LOKOqr94qjLva7bE5pnfm2KHJwsKz69J4KMxgWLznbpBJzy8vQfCayEk3vLZnZQ==
|
||||||
|
|
||||||
"@reach/auto-id@^0.2.0":
|
"@reach/auto-id@0.2.0", "@reach/auto-id@^0.2.0":
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.2.0.tgz#97f9e48fe736aa5c6f4f32cf73c1f19d005f8550"
|
resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.2.0.tgz#97f9e48fe736aa5c6f4f32cf73c1f19d005f8550"
|
||||||
integrity sha512-lVK/svL2HuQdp7jgvlrLkFsUx50Az9chAhxpiPwBqcS83I2pVWvXp98FOcSCCJCV++l115QmzHhFd+ycw1zLBg==
|
integrity sha512-lVK/svL2HuQdp7jgvlrLkFsUx50Az9chAhxpiPwBqcS83I2pVWvXp98FOcSCCJCV++l115QmzHhFd+ycw1zLBg==
|
||||||
|
@ -908,6 +908,13 @@
|
||||||
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.0.3.tgz#2ea3dcc369ab22bd9f050a92ea319321356a61e8"
|
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.0.3.tgz#2ea3dcc369ab22bd9f050a92ea319321356a61e8"
|
||||||
integrity sha1-LqPcw2mrIr2fBQqS6jGTITVqYeg=
|
integrity sha1-LqPcw2mrIr2fBQqS6jGTITVqYeg=
|
||||||
|
|
||||||
|
"@reach/portal@^0.2.1":
|
||||||
|
version "0.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.2.1.tgz#07720b999e0063a9e179c14dbdc60fd991cfc9fa"
|
||||||
|
integrity sha512-pUQ0EtCcYm4ormEjJmdk4uzZCxOpaRHB8FDKJXy6q6GqRqQwZ4lAT1f2Tvw0DAmULmyZTpe1/heXY27Tdnct+Q==
|
||||||
|
dependencies:
|
||||||
|
"@reach/component-component" "^0.1.3"
|
||||||
|
|
||||||
"@reach/rect@^0.2.1":
|
"@reach/rect@^0.2.1":
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@reach/rect/-/rect-0.2.1.tgz#7343020174c90e2290b844d17c03fd9c78e6b601"
|
resolved "https://registry.yarnpkg.com/@reach/rect/-/rect-0.2.1.tgz#7343020174c90e2290b844d17c03fd9c78e6b601"
|
||||||
|
@ -925,11 +932,33 @@
|
||||||
"@reach/utils" "^0.2.2"
|
"@reach/utils" "^0.2.2"
|
||||||
warning "^4.0.2"
|
warning "^4.0.2"
|
||||||
|
|
||||||
|
"@reach/tooltip@^0.2.1":
|
||||||
|
version "0.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@reach/tooltip/-/tooltip-0.2.1.tgz#70a80d6defedee53cedf5480cd3d37dfb20020d0"
|
||||||
|
integrity sha512-3O7oXoymNkEAolHN9WbuspY7mA3zIOrTaibmYkKFtGT6FgyBrAQyOQn1ZhBuSza6RjSIkEtFRpbDEKK1UJEI6A==
|
||||||
|
dependencies:
|
||||||
|
"@reach/auto-id" "0.2.0"
|
||||||
|
"@reach/portal" "^0.2.1"
|
||||||
|
"@reach/rect" "^0.2.1"
|
||||||
|
"@reach/utils" "^0.2.3"
|
||||||
|
"@reach/visually-hidden" "^0.1.4"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
"@reach/utils@^0.2.2":
|
"@reach/utils@^0.2.2":
|
||||||
version "0.2.2"
|
version "0.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.2.2.tgz#c3a05ae9fd1f921988ae8a89b5a0d28d1a2b92df"
|
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.2.2.tgz#c3a05ae9fd1f921988ae8a89b5a0d28d1a2b92df"
|
||||||
integrity sha512-jYeIi46AA5jh2gfdXD/nInUYfeLp3girRafiajP7AVHF6B4hpYAzUSx/ZH4xmPyf5alut5rml2DHxrv+X+Xu+A==
|
integrity sha512-jYeIi46AA5jh2gfdXD/nInUYfeLp3girRafiajP7AVHF6B4hpYAzUSx/ZH4xmPyf5alut5rml2DHxrv+X+Xu+A==
|
||||||
|
|
||||||
|
"@reach/utils@^0.2.3":
|
||||||
|
version "0.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.2.3.tgz#820f6a6af4301b4c5065cfc04bb89e6a3d1d723f"
|
||||||
|
integrity sha512-zM9rA8jDchr05giMhL95dPeYkK67cBQnIhCVrOKKqgWGsv+2GE/HZqeptvU4zqs0BvIqsThwov+YxVNVh5csTQ==
|
||||||
|
|
||||||
|
"@reach/visually-hidden@^0.1.4":
|
||||||
|
version "0.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.1.4.tgz#0dc4ecedf523004337214187db70a46183bd945b"
|
||||||
|
integrity sha512-QHbzXjflSlCvDd6vJwdwx16mSB+vUCCQMiU/wK/CgVNPibtpEiIbisyxkpZc55DyDFNUIqP91rSUsNae+ogGDQ==
|
||||||
|
|
||||||
"@samverschueren/stream-to-observable@^0.3.0":
|
"@samverschueren/stream-to-observable@^0.3.0":
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
|
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
|
||||||
|
@ -9447,11 +9476,6 @@ react-paginate@^5.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
prop-types "^15.6.1"
|
prop-types "^15.6.1"
|
||||||
|
|
||||||
react-portal-tooltip@^2.4.7:
|
|
||||||
version "2.4.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-portal-tooltip/-/react-portal-tooltip-2.4.7.tgz#77fd4a75760afecd116736c480485aa22cbbb740"
|
|
||||||
integrity sha512-ZOuCKpvu8rr6aS6nNjckd78qEgWyHZnKidZ9Yqrd0HR5K9NYJ7csb4duc0kEXeG8DiCWsLPU70o1EVP+MxlINg==
|
|
||||||
|
|
||||||
react-pose@^4.0.5:
|
react-pose@^4.0.5:
|
||||||
version "4.0.8"
|
version "4.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/react-pose/-/react-pose-4.0.8.tgz#91bdfafde60e4096e7878a35dcc77715bed68f24"
|
resolved "https://registry.yarnpkg.com/react-pose/-/react-pose-4.0.8.tgz#91bdfafde60e4096e7878a35dcc77715bed68f24"
|
||||||
|
@ -11453,11 +11477,6 @@ uniq@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
|
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
|
||||||
integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
|
integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
|
||||||
|
|
||||||
uniqid@^5.0.3:
|
|
||||||
version "5.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/uniqid/-/uniqid-5.0.3.tgz#917968310edc868d50df6c44f783f32c7d80fac1"
|
|
||||||
integrity sha512-R2qx3X/LYWSdGRaluio4dYrPXAJACTqyUjuyXHoJLBUOIfmMcnYOyY2d6Y4clZcIz5lK6ZaI0Zzmm0cPfsIqzQ==
|
|
||||||
|
|
||||||
uniqs@^2.0.0:
|
uniqs@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
|
resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
|
||||||
|
|
Loading…
Reference in a new issue