Notifications: use fetched urls instead of resolving (#820)
* Fix avatar occasionally stuck in spaceman ## Issue Sometimes, we'll see a channel profile (e.g. in upper-right, in channel selector) stuck in the fallback Spaceman image. This is due to OptimizedImage always starting with a blank src, and updated later when the mounted size has been determined. ChannelThumbnail, which uses OptimizedImage, captured the `onError` due to blank src. ## Fix Don't mount the <img> until the optimum size has been determined. * FileThumbnail: skip resolve if thumbnail url is specified * UriIndicator: skip resolve if channel info is specified * Notifications: disable batch resolve + use fetched data if available + fallback to resolve if n/a The fallback is using the individual resolve when no direct data is provided and claim is undefined.
This commit is contained in:
parent
d73504d69c
commit
1e7e7a7b7a
6 changed files with 70 additions and 30 deletions
3
flow-typed/notification.js
vendored
3
flow-typed/notification.js
vendored
|
@ -115,11 +115,14 @@ declare type WebNotification = {
|
||||||
},
|
},
|
||||||
dynamic: {
|
dynamic: {
|
||||||
comment_author: string,
|
comment_author: string,
|
||||||
|
comment_author_thumbnail?: string,
|
||||||
reply_author: string,
|
reply_author: string,
|
||||||
hash: string,
|
hash: string,
|
||||||
claim_title: string,
|
claim_title: string,
|
||||||
|
claim_thumbnail?: string,
|
||||||
comment?: string,
|
comment?: string,
|
||||||
channel_url: string,
|
channel_url: string,
|
||||||
|
channel_thumbnail?: string,
|
||||||
},
|
},
|
||||||
email: {},
|
email: {},
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,10 +30,10 @@ function FileThumbnail(props: Props) {
|
||||||
const isGif = thumbnail && thumbnail.endsWith('gif');
|
const isGif = thumbnail && thumbnail.endsWith('gif');
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!hasResolvedClaim && uri) {
|
if (!hasResolvedClaim && uri && !passedThumbnail) {
|
||||||
doResolveUri(uri);
|
doResolveUri(uri);
|
||||||
}
|
}
|
||||||
}, [hasResolvedClaim, uri, doResolveUri]);
|
}, [hasResolvedClaim, uri, doResolveUri, passedThumbnail]);
|
||||||
|
|
||||||
if (!allowGifs && isGif) {
|
if (!allowGifs && isGif) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -50,11 +50,12 @@ export default function Notification(props: Props) {
|
||||||
const stickerFromComment = isCommentNotification && commentText && parseSticker(commentText);
|
const stickerFromComment = isCommentNotification && commentText && parseSticker(commentText);
|
||||||
const notificationTarget = getNotificationTarget();
|
const notificationTarget = getNotificationTarget();
|
||||||
|
|
||||||
const creatorIcon = (channelUrl) => (
|
const creatorIcon = (channelUrl, channelThumbnail) => (
|
||||||
<UriIndicator uri={channelUrl} link>
|
<UriIndicator uri={channelUrl} link channelInfo={{ uri: channelUrl, name: '' }}>
|
||||||
<ChannelThumbnail small uri={channelUrl} />
|
<ChannelThumbnail small thumbnailPreview={channelThumbnail} uri={channelThumbnail ? undefined : channelUrl} />
|
||||||
</UriIndicator>
|
</UriIndicator>
|
||||||
);
|
);
|
||||||
|
|
||||||
let channelUrl;
|
let channelUrl;
|
||||||
let icon;
|
let icon;
|
||||||
switch (notification_rule) {
|
switch (notification_rule) {
|
||||||
|
@ -64,19 +65,19 @@ export default function Notification(props: Props) {
|
||||||
case RULE.COMMENT:
|
case RULE.COMMENT:
|
||||||
case RULE.CREATOR_COMMENT:
|
case RULE.CREATOR_COMMENT:
|
||||||
channelUrl = notification_parameters.dynamic.comment_author;
|
channelUrl = notification_parameters.dynamic.comment_author;
|
||||||
icon = creatorIcon(channelUrl);
|
icon = creatorIcon(channelUrl, notification_parameters?.dynamic?.comment_author_thumbnail);
|
||||||
break;
|
break;
|
||||||
case RULE.COMMENT_REPLY:
|
case RULE.COMMENT_REPLY:
|
||||||
channelUrl = notification_parameters.dynamic.reply_author;
|
channelUrl = notification_parameters.dynamic.reply_author;
|
||||||
icon = creatorIcon(channelUrl);
|
icon = creatorIcon(channelUrl, notification_parameters?.dynamic?.comment_author_thumbnail);
|
||||||
break;
|
break;
|
||||||
case RULE.NEW_CONTENT:
|
case RULE.NEW_CONTENT:
|
||||||
channelUrl = notification_parameters.dynamic.channel_url;
|
channelUrl = notification_parameters.dynamic.channel_url;
|
||||||
icon = creatorIcon(channelUrl);
|
icon = creatorIcon(channelUrl, notification_parameters?.dynamic?.channel_thumbnail);
|
||||||
break;
|
break;
|
||||||
case RULE.NEW_LIVESTREAM:
|
case RULE.NEW_LIVESTREAM:
|
||||||
channelUrl = notification_parameters.dynamic.channel_url;
|
channelUrl = notification_parameters.dynamic.channel_url;
|
||||||
icon = creatorIcon(channelUrl);
|
icon = creatorIcon(channelUrl, notification_parameters?.dynamic?.channel_thumbnail);
|
||||||
break;
|
break;
|
||||||
case RULE.DAILY_WATCH_AVAILABLE:
|
case RULE.DAILY_WATCH_AVAILABLE:
|
||||||
case RULE.DAILY_WATCH_REMIND:
|
case RULE.DAILY_WATCH_REMIND:
|
||||||
|
@ -111,7 +112,7 @@ export default function Notification(props: Props) {
|
||||||
let uriIndicator;
|
let uriIndicator;
|
||||||
const title = titleSplit.map((message, index) => {
|
const title = titleSplit.map((message, index) => {
|
||||||
if (channelName === message) {
|
if (channelName === message) {
|
||||||
uriIndicator = <UriIndicator uri={channelUrl} link />;
|
uriIndicator = <UriIndicator uri={channelUrl} link channelInfo={{ uri: channelUrl, name: channelName }} />;
|
||||||
fullTitle.push(' ');
|
fullTitle.push(' ');
|
||||||
const resultTitle = fullTitle;
|
const resultTitle = fullTitle;
|
||||||
fullTitle = [' '];
|
fullTitle = [' '];
|
||||||
|
@ -204,7 +205,11 @@ export default function Notification(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{notification_rule === RULE.NEW_CONTENT && (
|
{notification_rule === RULE.NEW_CONTENT && (
|
||||||
<FileThumbnail uri={notification_parameters.device.target} className="notificationContent__thumbnail" />
|
<FileThumbnail
|
||||||
|
uri={notification_parameters.device.target}
|
||||||
|
thumbnail={notification_parameters?.dynamic?.claim_thumbnail}
|
||||||
|
className="notificationContent__thumbnail"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{notification_rule === RULE.NEW_LIVESTREAM && (
|
{notification_rule === RULE.NEW_LIVESTREAM && (
|
||||||
<FileThumbnail
|
<FileThumbnail
|
||||||
|
|
|
@ -94,7 +94,7 @@ function OptimizedImage(props: Props) {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [src]);
|
}, [src]);
|
||||||
|
|
||||||
if (!src) {
|
if (!src || !optimizedSrc) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,13 @@ import React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
|
||||||
|
type ChannelInfo = {
|
||||||
|
uri: string,
|
||||||
|
name: string,
|
||||||
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isResolvingUri: boolean,
|
isResolvingUri: boolean,
|
||||||
channelUri: ?string,
|
|
||||||
link: ?boolean,
|
link: ?boolean,
|
||||||
claim: ?Claim,
|
claim: ?Claim,
|
||||||
hideAnonymous: boolean,
|
hideAnonymous: boolean,
|
||||||
|
@ -14,6 +18,7 @@ type Props = {
|
||||||
// Possibly because the resolve function is an arrow function that is passed in props?
|
// Possibly because the resolve function is an arrow function that is passed in props?
|
||||||
resolveUri: (string) => void,
|
resolveUri: (string) => void,
|
||||||
uri: string,
|
uri: string,
|
||||||
|
channelInfo: ?ChannelInfo, // Direct channel info to use, bypassing the need to resolve 'uri'.
|
||||||
// to allow for other elements to be nested within the UriIndicator
|
// to allow for other elements to be nested within the UriIndicator
|
||||||
children: ?Node,
|
children: ?Node,
|
||||||
inline: boolean,
|
inline: boolean,
|
||||||
|
@ -24,23 +29,53 @@ type Props = {
|
||||||
|
|
||||||
class UriIndicator extends React.PureComponent<Props> {
|
class UriIndicator extends React.PureComponent<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.resolve(this.props);
|
this.resolveClaim(this.props);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
this.resolve(this.props);
|
this.resolveClaim(this.props);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve = (props: Props) => {
|
resolveClaim = (props: Props) => {
|
||||||
const { isResolvingUri, resolveUri, claim, uri } = props;
|
const { isResolvingUri, resolveUri, claim, uri, channelInfo } = props;
|
||||||
|
|
||||||
if (!isResolvingUri && claim === undefined && uri) {
|
if (!channelInfo && !isResolvingUri && claim === undefined && uri) {
|
||||||
resolveUri(uri);
|
resolveUri(uri);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
resolveState = (channelInfo: ?ChannelInfo, claim: ?Claim, isLinkType: ?boolean) => {
|
||||||
|
if (channelInfo) {
|
||||||
|
return {
|
||||||
|
hasChannelData: true,
|
||||||
|
isAnonymous: false,
|
||||||
|
channelName: channelInfo.name,
|
||||||
|
channelLink: isLinkType ? channelInfo.uri : false,
|
||||||
|
};
|
||||||
|
} else if (claim) {
|
||||||
|
const signingChannel = claim.signing_channel && claim.signing_channel.amount;
|
||||||
|
const isChannelClaim = claim.value_type === 'channel';
|
||||||
|
const channelClaim = isChannelClaim ? claim : claim.signing_channel;
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasChannelData: Boolean(channelClaim),
|
||||||
|
isAnonymous: !signingChannel && !isChannelClaim,
|
||||||
|
channelName: channelClaim?.name,
|
||||||
|
channelLink: isLinkType ? channelClaim?.canonical_url || channelClaim?.permanent_url : false,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
hasChannelData: false,
|
||||||
|
isAnonymous: undefined,
|
||||||
|
channelName: undefined,
|
||||||
|
channelLink: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
channelInfo,
|
||||||
link,
|
link,
|
||||||
isResolvingUri,
|
isResolvingUri,
|
||||||
claim,
|
claim,
|
||||||
|
@ -52,7 +87,7 @@ class UriIndicator extends React.PureComponent<Props> {
|
||||||
className,
|
className,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!claim) {
|
if (!channelInfo && !claim) {
|
||||||
return (
|
return (
|
||||||
<span className={classnames('empty', className)}>
|
<span className={classnames('empty', className)}>
|
||||||
{isResolvingUri || claim === undefined ? __('Validating...') : __('[Removed]')}
|
{isResolvingUri || claim === undefined ? __('Validating...') : __('[Removed]')}
|
||||||
|
@ -60,9 +95,9 @@ class UriIndicator extends React.PureComponent<Props> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isChannelClaim = claim.value_type === 'channel';
|
const data = this.resolveState(channelInfo, claim, link);
|
||||||
const signingChannel = claim.signing_channel && claim.signing_channel.amount;
|
|
||||||
if (!signingChannel && !isChannelClaim) {
|
if (data.isAnonymous) {
|
||||||
if (hideAnonymous) {
|
if (hideAnonymous) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -74,15 +109,12 @@ class UriIndicator extends React.PureComponent<Props> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const channelClaim = isChannelClaim ? claim : claim.signing_channel;
|
if (data.hasChannelData) {
|
||||||
|
const { channelName, channelLink } = data;
|
||||||
if (channelClaim) {
|
|
||||||
const { name } = channelClaim;
|
|
||||||
const channelLink = link ? channelClaim.canonical_url || channelClaim.permanent_url : false;
|
|
||||||
|
|
||||||
const inner = (
|
const inner = (
|
||||||
<span dir="auto" className={classnames('channel-name', { 'channel-name--inline': inline })}>
|
<span dir="auto" className={classnames('channel-name', { 'channel-name--inline': inline })}>
|
||||||
{name}
|
{channelName}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ type Props = {
|
||||||
unseenCount: number,
|
unseenCount: number,
|
||||||
doSeeAllNotifications: () => void,
|
doSeeAllNotifications: () => void,
|
||||||
doReadNotifications: () => void,
|
doReadNotifications: () => void,
|
||||||
doNotificationList: (?Array<string>) => void,
|
doNotificationList: (?Array<string>, ?boolean) => void,
|
||||||
doNotificationCategories: () => void,
|
doNotificationCategories: () => void,
|
||||||
activeChannel: ?ChannelClaim,
|
activeChannel: ?ChannelClaim,
|
||||||
doCommentReactList: (Array<string>) => Promise<any>,
|
doCommentReactList: (Array<string>) => Promise<any>,
|
||||||
|
@ -97,7 +97,7 @@ export default function NotificationsPage(props: Props) {
|
||||||
try {
|
try {
|
||||||
const matchingCategory = arrayNotificationCategories.find((category) => category.name === name);
|
const matchingCategory = arrayNotificationCategories.find((category) => category.name === name);
|
||||||
if (matchingCategory) {
|
if (matchingCategory) {
|
||||||
doNotificationList(matchingCategory.types);
|
doNotificationList(matchingCategory.types, false);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e); // eslint-disable-line no-console
|
console.error(e); // eslint-disable-line no-console
|
||||||
|
|
Loading…
Reference in a new issue