lbry-desktop/ui/component/notification/view.jsx

270 lines
9.6 KiB
React
Raw Normal View History

2020-07-23 16:22:57 +02:00
// @flow
import { lazyImport } from 'util/lazyImport';
import { formatLbryUrlForWeb } from 'util/url';
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
import { NavLink } from 'react-router-dom';
import { PAGE_VIEW_QUERY, DISCUSSION_PAGE } from 'page/channel/view';
import { parseURI } from 'util/lbryURI';
import { RULE } from 'constants/notifications';
import { useHistory } from 'react-router';
import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import Button from 'component/button';
2020-07-23 16:22:57 +02:00
import ChannelThumbnail from 'component/channelThumbnail';
import classnames from 'classnames';
import DateTime from 'component/dateTime';
2020-11-02 17:51:08 +01:00
import FileThumbnail from 'component/fileThumbnail';
import Icon from 'component/common/icon';
import NotificationContentChannelMenu from 'component/notificationContentChannelMenu';
import React from 'react';
import UriIndicator from 'component/uriIndicator';
Make notification titles translatable ## Issue Desktop 7022: i18n: Notification title localization lost ## Approach The current code breaks up the string into an array of strings, so i18n is somewhat impossible. Since 99%¹ of dynamic notifications come with a `dynamic` property for all the replaceable values, we can actually reconstruct the string however we want. ¹ <sub>_as far as I can find, only `fiat_tip` does not provide the value via `dynamic`, i.e. hardcoded in the string. Boo._</sub> ### Benefits of this approach: - able to localize the string again - able to customize the string (e.g. making claim titles italic, fix typos, use more concise strings, etc.) ### Problems with this approach: - if the api overloads a particular notification type with several strings, then the approach is broken. - Ex. For the case of `comment` type, the overload is whether the comment is a normal comment or hyperchat. But I was able to replicate the logic to differentiate them, so all is good. - For the case of "livestream reminder in 30 minutes", we would have to inspect the string to differentiate against "is live streaming". I think this reminder is not being used, so I didn't do it yet. - some work is needed to maintain the code when the server side updates something. But we are already manually maintaining the i18n strings anyway, so it shouldn't be too much of a burden. - With the exception of the overload problem, any new notification type will simply pass through as un-localized, so things should continue to work, i.e. no need to update the front-end immediately 🤞
2022-02-14 10:04:12 +01:00
import { generateNotificationTitle } from './helpers/title';
import { generateNotificationText } from './helpers/text';
Re-design comment threads (#1489) * Redesign threadline and fetching state - threadline goes right below channel avatar, mimicking reddits implementation, has a increase effect on hover and is slimmer, creating more space for comments on screen - fetching state now replaces show/hide button, also mimicking reddit, and now says that it is loading, instead of a blank spinner, and also improves space a bit * Redesign comment threads - Allow for infinite comment chains - Can go back and forth between the pages - Can go back to all comments or to the first comment in the chain - Some other improvements, which include: - add title on non-drawer comment sections (couldn't see amount of comments) - fix Expandable component (would begin expanded and collapse after the effect runs, which looked bad and shifted the layout, now each comments greater than the set length begins collapsed) - used constants for consistency * Fix replying to last thread comment * Fix buttons condition (only on fetched comment to avoid deleted case) * Fix auto-scroll * Bring back instant feedback for Show More replies * Improve thread back links - Now going back to all comments links the top-level comment for easier navigation - Going back to ~ previous ~ now goes back into the chain instead of topmost level * Clear timeouts due to unrelated issue * Fix deep thread linked comment case and more scroll improvements * More minor changes * Flow * Fix commentList tile style * Fix long channel names overflowing on small screens * More scroll changes * Fix threadline * Revert "Fix long channel names overflowing on small screens" This reverts commit e4d2dc7da5861ed8136a60f3352e41a690cd4d33. * Fix replies fetch * Revert "Fix replies fetch" This reverts commit ec70054675a604a7a5f3764ba07c36bf7b0f49c8. * Cleanup and make smooth * Always use linked comment on threads * Cleanup * Higlight thread comment * Fix comment body styles
2022-05-16 12:22:13 +02:00
import { LINKED_COMMENT_QUERY_PARAM } from 'constants/comment';
2020-07-23 16:22:57 +02:00
const CommentCreate = lazyImport(() => import('component/commentCreate' /* webpackChunkName: "comments" */));
const CommentReactions = lazyImport(() => import('component/commentReactions' /* webpackChunkName: "comments" */));
const CommentsReplies = lazyImport(() => import('component/commentsReplies' /* webpackChunkName: "comments" */));
2020-07-23 16:22:57 +02:00
type Props = {
menuButton: boolean,
notification: WebNotification,
deleteNotification: () => void,
readNotification: () => void,
2020-07-23 16:22:57 +02:00
};
export default function Notification(props: Props) {
const { menuButton = false, notification, readNotification, deleteNotification } = props;
const { notification_rule, notification_parameters, is_read } = notification;
2020-07-23 16:22:57 +02:00
const { push } = useHistory();
2021-08-27 12:29:58 +02:00
const [isReplying, setReplying] = React.useState(false);
const [quickReply, setQuickReply] = React.useState();
const isCommentNotification =
notification_rule === RULE.COMMENT ||
notification_rule === RULE.COMMENT_REPLY ||
notification_rule === RULE.CREATOR_COMMENT;
const notificationTarget = getNotificationTarget();
const creatorIcon = (channelUrl, channelThumbnail) => (
<UriIndicator uri={channelUrl} link showAtSign channelInfo={{ uri: channelUrl, name: '' }}>
<ChannelThumbnail small thumbnailPreview={channelThumbnail} uri={channelThumbnail ? undefined : channelUrl} />
</UriIndicator>
);
let channelUrl;
2020-07-23 16:22:57 +02:00
let icon;
2020-08-21 21:44:54 +02:00
switch (notification_rule) {
case RULE.CREATOR_SUBSCRIBER:
2020-11-13 18:58:31 +01:00
icon = <Icon icon={ICONS.SUBSCRIBE} sectionIcon />;
2020-07-23 16:22:57 +02:00
break;
case RULE.COMMENT:
case RULE.CREATOR_COMMENT:
channelUrl = notification_parameters.dynamic.comment_author;
icon = creatorIcon(channelUrl, notification_parameters?.dynamic?.comment_author_thumbnail);
2020-07-23 16:22:57 +02:00
break;
case RULE.COMMENT_REPLY:
channelUrl = notification_parameters.dynamic.reply_author;
icon = creatorIcon(channelUrl, notification_parameters?.dynamic?.comment_author_thumbnail);
2020-09-08 22:56:48 +02:00
break;
case RULE.NEW_CONTENT:
channelUrl = notification_parameters.dynamic.channel_url;
icon = creatorIcon(channelUrl, notification_parameters?.dynamic?.channel_thumbnail);
2020-11-02 17:51:08 +01:00
break;
case RULE.NEW_LIVESTREAM:
channelUrl = notification_parameters.dynamic.channel_url;
icon = creatorIcon(channelUrl, notification_parameters?.dynamic?.channel_thumbnail);
2021-04-16 21:53:33 +02:00
break;
case RULE.WEEKLY_WATCH_REMINDER:
case RULE.DAILY_WATCH_AVAILABLE:
case RULE.DAILY_WATCH_REMIND:
case RULE.MISSED_OUT:
case RULE.REWARDS_APPROVAL_PROMPT:
2020-11-13 06:36:23 +01:00
icon = <Icon icon={ICONS.LBC} sectionIcon />;
2020-10-22 18:40:09 +02:00
break;
case RULE.FIAT_TIP:
icon = <Icon icon={ICONS.FINANCE} sectionIcon />;
break;
2020-07-23 16:22:57 +02:00
default:
2020-11-13 18:58:31 +01:00
icon = <Icon icon={ICONS.NOTIFICATION} sectionIcon />;
2020-07-23 16:22:57 +02:00
}
let notificationLink = formatLbryUrlForWeb(notificationTarget);
let urlParams = new URLSearchParams();
if (isCommentNotification && notification_parameters.dynamic.hash) {
Re-design comment threads (#1489) * Redesign threadline and fetching state - threadline goes right below channel avatar, mimicking reddits implementation, has a increase effect on hover and is slimmer, creating more space for comments on screen - fetching state now replaces show/hide button, also mimicking reddit, and now says that it is loading, instead of a blank spinner, and also improves space a bit * Redesign comment threads - Allow for infinite comment chains - Can go back and forth between the pages - Can go back to all comments or to the first comment in the chain - Some other improvements, which include: - add title on non-drawer comment sections (couldn't see amount of comments) - fix Expandable component (would begin expanded and collapse after the effect runs, which looked bad and shifted the layout, now each comments greater than the set length begins collapsed) - used constants for consistency * Fix replying to last thread comment * Fix buttons condition (only on fetched comment to avoid deleted case) * Fix auto-scroll * Bring back instant feedback for Show More replies * Improve thread back links - Now going back to all comments links the top-level comment for easier navigation - Going back to ~ previous ~ now goes back into the chain instead of topmost level * Clear timeouts due to unrelated issue * Fix deep thread linked comment case and more scroll improvements * More minor changes * Flow * Fix commentList tile style * Fix long channel names overflowing on small screens * More scroll changes * Fix threadline * Revert "Fix long channel names overflowing on small screens" This reverts commit e4d2dc7da5861ed8136a60f3352e41a690cd4d33. * Fix replies fetch * Revert "Fix replies fetch" This reverts commit ec70054675a604a7a5f3764ba07c36bf7b0f49c8. * Cleanup and make smooth * Always use linked comment on threads * Cleanup * Higlight thread comment * Fix comment body styles
2022-05-16 12:22:13 +02:00
urlParams.append(LINKED_COMMENT_QUERY_PARAM, notification_parameters.dynamic.hash);
}
2021-09-22 17:28:32 +02:00
let channelName;
if (channelUrl) {
try {
({ claimName: channelName } = parseURI(channelUrl));
} catch (e) {}
}
try {
const { isChannel } = parseURI(notificationTarget);
if (isChannel) urlParams.append(PAGE_VIEW_QUERY, DISCUSSION_PAGE);
} catch (e) {}
notificationLink += `?${urlParams.toString()}`;
const navLinkProps = { to: notificationLink, onClick: (e) => e.stopPropagation() };
function getNotificationTarget() {
switch (notification_rule) {
case RULE.WEEKLY_WATCH_REMINDER:
case RULE.DAILY_WATCH_AVAILABLE:
case RULE.DAILY_WATCH_REMIND:
return `/$/${PAGES.CHANNELS_FOLLOWING}`;
case RULE.MISSED_OUT:
case RULE.REWARDS_APPROVAL_PROMPT:
return `/$/${PAGES.REWARDS_VERIFY}?redirect=/$/${PAGES.REWARDS}`;
2022-06-23 15:14:47 +02:00
case RULE.FIAT_TIP:
return `/$/${PAGES.WALLET}?fiatType=incoming&tab=fiat-payment-history&currency=fiat`;
default:
return notification_parameters.device.target;
2020-08-21 21:44:54 +02:00
}
}
2020-08-21 21:44:54 +02:00
function handleNotificationClick() {
if (!is_read) readNotification();
if (menuButton && notificationLink) push(notificationLink);
2020-08-21 21:44:54 +02:00
}
2020-07-23 16:22:57 +02:00
const Wrapper = menuButton
? (props: { children: any }) => (
2020-08-21 21:44:54 +02:00
<MenuItem className="menu__link--notification" onSelect={handleNotificationClick}>
2020-07-23 16:22:57 +02:00
{props.children}
</MenuItem>
)
2020-08-21 21:44:54 +02:00
: notificationLink
? (props: { children: any }) => (
2021-08-27 12:29:58 +02:00
<NavLink {...navLinkProps} className="menu__link--notification" onClick={handleNotificationClick}>
{props.children}
</NavLink>
2020-08-21 21:44:54 +02:00
)
: (props: { children: any }) => (
<span
2020-12-14 19:52:17 +01:00
className={is_read ? 'menu__link--notification-nolink' : 'menu__link--notification'}
2020-08-21 21:44:54 +02:00
onClick={handleNotificationClick}
>
{props.children}
</span>
2020-07-23 16:22:57 +02:00
);
return (
<div className={classnames('notification__wrapper', { 'notification__wrapper--unread': !is_read })}>
2021-08-27 12:29:58 +02:00
<Wrapper>
2020-07-23 16:22:57 +02:00
<div className="notification__icon">{icon}</div>
<div className="notificationContent__wrapper">
2020-11-03 19:45:36 +01:00
<div className="notification__content">
<div className="notificationText__wrapper">
Make notification titles translatable ## Issue Desktop 7022: i18n: Notification title localization lost ## Approach The current code breaks up the string into an array of strings, so i18n is somewhat impossible. Since 99%¹ of dynamic notifications come with a `dynamic` property for all the replaceable values, we can actually reconstruct the string however we want. ¹ <sub>_as far as I can find, only `fiat_tip` does not provide the value via `dynamic`, i.e. hardcoded in the string. Boo._</sub> ### Benefits of this approach: - able to localize the string again - able to customize the string (e.g. making claim titles italic, fix typos, use more concise strings, etc.) ### Problems with this approach: - if the api overloads a particular notification type with several strings, then the approach is broken. - Ex. For the case of `comment` type, the overload is whether the comment is a normal comment or hyperchat. But I was able to replicate the logic to differentiate them, so all is good. - For the case of "livestream reminder in 30 minutes", we would have to inspect the string to differentiate against "is live streaming". I think this reminder is not being used, so I didn't do it yet. - some work is needed to maintain the code when the server side updates something. But we are already manually maintaining the i18n strings anyway, so it shouldn't be too much of a burden. - With the exception of the overload problem, any new notification type will simply pass through as un-localized, so things should continue to work, i.e. no need to update the front-end immediately 🤞
2022-02-14 10:04:12 +01:00
<div className="notification__title">
{generateNotificationTitle(notification_rule, notification_parameters, channelName)}
</div>
{generateNotificationText(notification_rule, notification_parameters)}
2020-11-03 19:45:36 +01:00
</div>
{notification_rule === RULE.NEW_CONTENT && (
<FileThumbnail
uri={notification_parameters.device.target}
thumbnail={notification_parameters?.dynamic?.claim_thumbnail}
className="notificationContent__thumbnail"
/>
2021-04-16 21:53:33 +02:00
)}
{notification_rule === RULE.NEW_LIVESTREAM && (
2021-04-16 21:53:33 +02:00
<FileThumbnail
thumbnail={notification_parameters.device.image_url}
className="notificationContent__thumbnail"
2021-04-16 21:53:33 +02:00
/>
2020-08-21 21:44:54 +02:00
)}
</div>
<div className="notification__extra">
2021-08-27 12:29:58 +02:00
{!is_read && (
<Button
className="notification__markSeen"
2021-08-27 12:29:58 +02:00
onClick={(e) => {
e.stopPropagation();
readNotification();
2021-08-27 12:29:58 +02:00
}}
/>
)}
<div className="notification__time">
2020-09-15 15:54:05 +02:00
<DateTime timeAgo date={notification.active_at} />
</div>
2020-07-23 16:22:57 +02:00
</div>
</div>
<div className="notification__menu">
<Menu>
<MenuButton
className="menu__button notification__menuButton"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<Icon size={18} icon={ICONS.MORE_VERTICAL} />
</MenuButton>
<MenuList className="menu__list">
<MenuItem className="menu__link" onSelect={() => deleteNotification()}>
<Icon aria-hidden icon={ICONS.DELETE} />
{__('Delete')}
</MenuItem>
{notification_rule === RULE.NEW_CONTENT && channelUrl ? (
<NotificationContentChannelMenu uri={channelUrl} />
) : null}
</MenuList>
</Menu>
</div>
2021-08-27 12:29:58 +02:00
</Wrapper>
{isCommentNotification && (
<div>
<div className="notification__reactions">
<Button
label={__('Reply')}
className="comment__action"
onClick={() => setReplying(!isReplying)}
icon={ICONS.REPLY}
/>
<CommentReactions
uri={notificationTarget}
commentId={notification_parameters.dynamic.hash}
hideCreatorLike
/>
</div>
{isReplying && (
<React.Suspense fallback={null}>
<CommentCreate
isReply
uri={notificationTarget}
parentId={notification_parameters.dynamic.hash}
onDoneReplying={() => setReplying(false)}
onCancelReplying={() => setReplying(false)}
setQuickReply={setQuickReply}
supportDisabled
shouldFetchComment
/>
</React.Suspense>
2021-08-27 12:29:58 +02:00
)}
{quickReply && (
<CommentsReplies
uri={notificationTarget}
parentId={notification_parameters.dynamic.hash}
numDirectReplies={1}
supportDisabled
/>
)}
</div>
)}
</div>
2020-07-23 16:22:57 +02:00
);
}