@blocklet/ui-react
Version:
Some useful front-end web components that can be used in Blocklets.
126 lines (108 loc) • 4.34 kB
JSX
import PropTypes from 'prop-types';
import { useCallback, useEffect, useMemo } from 'react';
import { IconButton, Badge } from '@mui/material';
import { useSnackbar } from 'notistack';
import NotificationsOutlinedIcon from '@arcblock/icons/lib/Notification';
import { useCreation } from 'ahooks';
import { EVENTS, WELLKNOWN_SERVICE_PATH_PREFIX } from '@abtnode/constant';
import useBrowser from '@arcblock/react-hooks/lib/useBrowser';
import { joinURL, withQuery } from 'ufo';
import { useListenWsClient } from './ws';
import NotificationSnackbar from '../Notifications/Snackbar';
import { compareVersions } from '../utils';
const viewAllUrl = joinURL(WELLKNOWN_SERVICE_PATH_PREFIX, 'user', 'notifications');
const getNotificationLink = (notification) => {
return withQuery(viewAllUrl, {
id: notification.id,
severity: notification.severity || 'all',
componentDid: notification.source === 'system' ? 'system' : notification.componentDid || 'all',
});
};
export default function NotificationAddon({ session = {} }) {
const { unReadCount, user, setUnReadCount } = session;
const userDid = useCreation(() => user?.did, [user]);
const { enqueueSnackbar } = useSnackbar();
const browser = useBrowser();
// 在 ArcSphere 和 Wallet 端隐藏, 消息的 toast 提示,因为有 native 的通知
const hiddenNotificationToast = useMemo(() => {
return browser.arcSphere || browser.wallet;
}, [browser]);
const serverVersion = useCreation(() => {
return window.blocklet?.serverVersion;
}, []);
const wsClient = useListenWsClient('user');
const listenEvent = useCreation(
() => `${window.blocklet.did}/${userDid}/${EVENTS.NOTIFICATION_BLOCKLET_CREATE}`,
[userDid]
);
const readListenEvent = useCreation(
() => `${window.blocklet.did}/${userDid}/${EVENTS.NOTIFICATION_BLOCKLET_READ}`,
[userDid]
);
const listenCallback = useCallback(
(notification) => {
const { receivers } = notification ?? {};
const { receiver: notificationReceiver } = receivers[0] ?? {};
if (notificationReceiver === userDid) {
setUnReadCount((x) => x + 1);
// 显示通知, 如果是系统通知则不需要显示, 如果是移动端不需要显示
// 兼容代码,如果 server 没有升级那么不需要提示
const isCompatible = compareVersions(serverVersion, '1.16.42-beta-20250407');
if (!hiddenNotificationToast && notification.source === 'component' && isCompatible) {
const link = getNotificationLink(notification);
const { severity, description } = notification || {};
const disableAutoHide = ['error', 'warning'].includes(severity) || notification.sticky;
enqueueSnackbar(description, {
variant: severity,
autoHideDuration: disableAutoHide ? null : 5000,
// eslint-disable-next-line react/no-unstable-nested-components
content: (key) => <NotificationSnackbar viewAllUrl={link} keyId={key} notification={notification} />,
});
}
}
},
[userDid, setUnReadCount, enqueueSnackbar, serverVersion, hiddenNotificationToast]
);
const readListenCallback = useCallback(
(data) => {
const { receiver, readCount } = data ?? {};
if (receiver === userDid) {
setUnReadCount((x) => Math.max(x - readCount, 0));
}
},
[userDid, setUnReadCount]
);
useEffect(() => {
if (wsClient) {
wsClient.on(listenEvent, listenCallback);
wsClient.on(readListenEvent, readListenCallback);
}
return () => {
if (wsClient) {
wsClient.off(listenEvent, listenCallback);
wsClient.off(readListenEvent, readListenCallback);
}
};
}, [wsClient, setUnReadCount, listenCallback, listenEvent, readListenCallback, readListenEvent]);
if (!session.user || !viewAllUrl) {
return null;
}
return (
<IconButton
size="medium"
variant="outlined"
href={viewAllUrl}
sx={{
'&:hover': {
borderRadius: '50%',
},
}}>
<Badge badgeContent={unReadCount} color="error" invisible={unReadCount === 0}>
<NotificationsOutlinedIcon style={{ width: 'auto', height: 24 }} />
</Badge>
</IconButton>
);
}
NotificationAddon.propTypes = {
session: PropTypes.object,
};