UNPKG

@blocklet/ui-react

Version:

Some useful front-end web components that can be used in Blocklets.

166 lines (154 loc) 6 kB
import 'iconify-icon'; /* eslint-disable no-shadow */ import { useMemo, useLayoutEffect, use } from 'react'; import PropTypes from 'prop-types'; import { SessionContext } from '@arcblock/did-connect/lib/Session'; import { useLocaleContext } from '@arcblock/ux/lib/Locale/context'; import UxImg from '@arcblock/ux/lib/Img'; import UxDashboard from '@arcblock/ux/lib/Layout/dashboard'; import useBlockletLogo from '@arcblock/ux/lib/hooks/use-blocklet-logo'; import { BlockletMetaProps, SessionManagerProps } from '../types'; import { mapRecursive, flatRecursive, matchPaths } from '../utils'; import { publicPath, formatBlockletInfo, getLocalizedNavigation, filterNavByRole } from '../blocklets'; import HeaderAddons from '../common/header-addons'; import { useWalletHiddenTopbar } from '../common/wallet-hidden-topbar'; /** * 专门用于 (composable) blocklet 的 Dashboard 组件, 解析 blocklet meta 中 section 为 dashboard 的 navigation 数据, 渲染一个 UX Dashboard */ function Dashboard({ meta = {}, fallbackUrl = publicPath, invalidPathFallback = null, headerAddons = undefined, sessionManagerProps = { showRole: true, // dashboard 默认退出登录行为: 跳转到 (root) blocklet 首页 onLogout: () => { // 这里是安全的 window.location.href = publicPath; }, }, links = [], showDomainWarningDialog = true, ...rest }) { useWalletHiddenTopbar(); const sessionCtx = use(SessionContext); const user = sessionCtx?.session?.user; const userRole = user?.role; const { locale } = useLocaleContext() || {}; const formattedBlocklet = useMemo(() => { const blocklet = Object.assign({}, window.blocklet, meta); try { return formatBlockletInfo(blocklet); } catch (e) { console.error('Failed to format blocklet info', e, blocklet); return blocklet; } }, [meta]); const { localizedNav, flattened, matchedIndex } = useMemo(() => { // eslint-disable-next-line @typescript-eslint/no-shadow let localizedNav = getLocalizedNavigation(formattedBlocklet?.navigation?.dashboard, locale) || []; // 根据 role 筛选 nav 数据 localizedNav = filterNavByRole(localizedNav, userRole); // 将 nav 数据处理成 ux dashboard 需要的格式 localizedNav = mapRecursive( localizedNav, (item) => { let icon = null; if (item.icon) { if (item.icon.startsWith('http') || item.icon.startsWith('data:')) { icon = <UxImg src={item.icon} />; } else { icon = <iconify-icon height="100%" width="100%" icon={item.icon} />; } } return { id: item.id, title: item.title, url: item.link, icon, // https://github.com/ArcBlock/ux/issues/755#issuecomment-1208692620 external: true, children: item.items, }; }, 'items' ); // 展平后使用 matchPaths 检测 link#active 状态 // eslint-disable-next-line @typescript-eslint/no-shadow const flattened = flatRecursive(localizedNav).filter((item) => !!item.url); // eslint-disable-next-line @typescript-eslint/no-shadow const matchedIndex = matchPaths(flattened.map((item) => item.url)); if (matchedIndex !== -1) { flattened[matchedIndex].active = true; } return { localizedNav, flattened, matchedIndex }; }, [formattedBlocklet, locale, userRole]); const allLinks = typeof links === 'function' ? links(localizedNav) : [...localizedNav, ...links]; const appLogo = useBlockletLogo({ meta, }); // 页面初始化时, 如果当前用户没有权限访问任何导航菜单 (比如登录时未提供 VC 导致无权限), 则跳转到 fallbackUrl // 未认证 (user 为空) 时不做处理, 这种情况的页面跳转逻辑一般由应用自行处理 useLayoutEffect(() => { if (!!user && !flattened?.length && fallbackUrl) { // 这里是安全的 window.location.href = fallbackUrl; } // eslint-disable-next-line react-hooks/exhaustive-deps }, [fallbackUrl]); // 导航菜单变动且存在可用菜单但无匹配项时 (如切换 passport), 跳转到首个菜单项 useLayoutEffect(() => { if (!!user && !!flattened?.length && matchedIndex === -1) { if (invalidPathFallback) { invalidPathFallback(); } else { // FIXME: @zhanghan 暂时取消跳转首个 url 的行为 // window.location.href = flattened[0]?.url || publicPath; } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [invalidPathFallback, flattened, matchedIndex]); if (!formattedBlocklet.appName) { return null; } const { appName } = formattedBlocklet; // eslint-disable-next-line @typescript-eslint/naming-convention const _headerAddons = ( <HeaderAddons formattedBlocklet={formattedBlocklet} addons={headerAddons} sessionManagerProps={sessionManagerProps} showDomainWarningDialog={showDomainWarningDialog} /> ); return ( <UxDashboard title={appName} fullWidth sidebarWidth={128} legacy={false} links={allLinks} {...rest} headerProps={{ homeLink: publicPath, logo: <img src={appLogo} alt="logo" />, addons: _headerAddons, ...rest.headerProps, }} /> ); } Dashboard.propTypes = { meta: BlockletMetaProps, // 如果当前用户没有权限访问任何导航菜单, 则自动跳转到 fallbackUrl, 默认值为 publicPath, 设置为 null 表示禁用自动跳转 fallbackUrl: PropTypes.string, // 当前路径未匹配任何 nav links 时的 fallback, 默认行为跳转到首个可用的 nav link invalidPathFallback: PropTypes.func, headerAddons: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), sessionManagerProps: SessionManagerProps, links: PropTypes.oneOfType([PropTypes.array, PropTypes.func]), showDomainWarningDialog: PropTypes.bool, }; export default Dashboard;