import React, { useState, useEffect, useMemo } from 'react'; import { graphql, Link } from 'gatsby'; import { Layout as AntLayout, Anchor, Affix, BackTop, Menu, Badge } from 'antd'; import { createFromIconfontCN, MenuFoldOutlined, MenuUnfoldOutlined, VerticalAlignTopOutlined, } from '@ant-design/icons'; import { groupBy, debounce, each, filter, size } from 'lodash-es'; import { useTranslation } from 'react-i18next'; import Drawer from 'rc-drawer'; import { useMedia } from 'react-use'; import Article from '../components/Article'; import SEO from '../components/Seo'; import PlayGround from '../components/PlayGround'; import NavigatorBanner from '../components/NavigatorBanner'; import Announcement from '../components/Announcement'; import { usePrevAndNext } from '../hooks'; import styles from './markdown.module.less'; const { SubMenu } = Menu; const { Link: AnchorLink } = Anchor; /** 是否为 Examples 图表示例的首页 */ let gallery = false; const MenuIcon = createFromIconfontCN({ scriptUrl: '//at.alicdn.com/t/font_470089_1lnym745udm.js', // generated by iconfont.cn }); const BANNER_LOCALSTORAGE_KEY = 'antv_gallery_banner'; const renderAnchorItems = (edges: any[]) => edges .filter((edge: any) => { const { node: { fields: { slug }, }, } = edge; if ( slug.endsWith('/API') || slug.endsWith('/design') || slug.endsWith('/gallery') ) { return false; } return true; }) .sort((a: any, b: any) => { const { node: { frontmatter: { order: aOrder }, }, } = a; const { node: { frontmatter: { order: bOrder }, }, } = b; return aOrder - bOrder; }) .map((edge: any) => { const { node: { frontmatter: { title, icon }, fields: { slug }, }, } = edge; return ( {icon && ( )} {title} } /> ); }); const getMenuItemLocaleKey = (slug = '') => { const slugPieces = slug.split('/'); const menuItemLocaleKey = slugPieces .slice(slugPieces.indexOf('examples') + 1) .filter((key) => key) .join('/'); return menuItemLocaleKey; }; const getExampleOrder = ({ groupedEdgeKey = '', examples = [], groupedEdges = {}, }: { groupedEdgeKey: string; examples: any[]; groupedEdges: { [key: string]: any[]; }; }): number => { const key = getMenuItemLocaleKey(groupedEdgeKey); if (examples.find((item) => item.slug === key)) { return (examples.findIndex((item) => item.slug === key) || 0) + 100; } if (!groupedEdges[groupedEdgeKey] && !groupedEdges[groupedEdgeKey].length) { return 0; } return groupedEdges[groupedEdgeKey][0].node.frontmatter.order || 0; }; export default function Template({ data, // this prop will be injected by the GraphQL query below. location, pageContext, }: { data: any; location: Location; pageContext: { exampleSections: any; allDemos?: any[]; }; }): React.ReactNode { const { allMarkdownRemark, site } = data; // data.markdownRemark holds our post data const { edges = [] } = allMarkdownRemark; const edgesInExamples = edges; const pathWithoutTrailingSlashes = location.pathname.replace(/\/$/, ''); const { node: markdownRemark } = edgesInExamples.find((edge: any) => { const { fields: { slug }, } = edge.node; if ( /\/examples\/.*\/API$/.test(pathWithoutTrailingSlashes) || /\/examples\/.*\/design$/.test(pathWithoutTrailingSlashes) ) { return pathWithoutTrailingSlashes.indexOf(slug) >= 0; } return ( pathWithoutTrailingSlashes === slug || pathWithoutTrailingSlashes.endsWith(slug) ); }) || {}; if (!markdownRemark) { return null; } const { frontmatter } = markdownRemark; const { siteMetadata: { examples = [], galleryMenuCloseAll = false }, } = site; const { t, i18n } = useTranslation(); const groupedEdges = groupBy( edgesInExamples, ({ node: { fields: { slug: slugString }, }, }: any) => { // API.md and deisgn.md if (slugString.endsWith('/API') || slugString.endsWith('/design')) { return slugString.split('/').slice(0, -2).join('/'); } // index.md return slugString.split('/').slice(0, -1).join('/'); }, ); const { exampleSections = {}, allDemos = [] } = pageContext; const [prev, next] = usePrevAndNext(); // 获取 demo 的 Category 分类 const getDemoCategory = (demo: any, lang = i18n.language) => { if (!demo.postFrontmatter || !demo.postFrontmatter[lang]) { return 'OTHER'; } return demo.postFrontmatter[lang].title; }; const allDemosInCategory = groupBy(allDemos || [], getDemoCategory); const Categories = Object.keys(allDemosInCategory).sort( (a: string, b: string) => { if (a === 'OTHER') { return -1; } if (b === 'OTHER') { return 1; } return ( allDemosInCategory[a][0].postFrontmatter[i18n.language].order - allDemosInCategory[b][0].postFrontmatter[i18n.language].order ); }, ); const [openKeys, setOpenKeys] = useState(() => // 是否默认收起所有 sub menu galleryMenuCloseAll ? [] : Object.keys(groupedEdges), ); const [selectedKeys, setSelectedKeys] = useState([]); const onAnchorLinkChange = debounce((currentActiveLink: string) => { let currentSlug = ''; edges.forEach((edge: any) => { const { node: { frontmatter: { title }, fields: { slug }, }, } = edge; if (`#category-${title.replace(/\s/g, '')}` === currentActiveLink) { currentSlug = slug; } }); setSelectedKeys([currentSlug]); if (currentActiveLink) { const link = document.querySelector(`a[href='${currentActiveLink}']`); if (link) { const anchor = link?.parentNode as Element; anchor.scrollIntoView({ block: 'center', }); } } }, 300); // 提取出筛选 和 排序的方法 好在获取treeData 的时候使用 const groupedEdgesDataEdit = Object.keys(groupedEdges) .filter((key) => key.startsWith(`/${i18n.language}/`)) .sort((a: string, b: string) => { const aOrder = getExampleOrder({ groupedEdgeKey: a, examples, groupedEdges, }); const bOrder = getExampleOrder({ groupedEdgeKey: b, examples, groupedEdges, }); return aOrder - bOrder; }); const menu = ( setOpenKeys(currentOpenKeys as string[]) } forceSubMenuRender > {groupedEdgesDataEdit.map((slugString) => { const slugPieces = slugString.split('/'); if (slugPieces.length <= 3) { return renderAnchorItems(groupedEdges[slugString]); } const menuItemLocaleKey = getMenuItemLocaleKey(slugString); const doc = examples.find((item: any) => item.slug === menuItemLocaleKey) || {}; return ( {doc.icon && ( )} {doc && doc.title ? doc.title[i18n.language] : menuItemLocaleKey} } > {renderAnchorItems(groupedEdges[slugString])} ); })} ); const getTreeData = () => groupedEdgesDataEdit.map((slugString) => { const menuItemLocaleKey = getMenuItemLocaleKey(slugString); const doc = examples.find((item: any) => item.slug === menuItemLocaleKey) || {}; return { title: doc && doc.title ? doc.title[i18n.language] : menuItemLocaleKey, value: slugString, icon: doc.icon, children: groupedEdges[slugString].filter((edge) => { const { node: { fields: { slug }, }, } = edge; if ( slug.endsWith('/API') || slug.endsWith('/design') || slug.endsWith('/gallery') ) { return false; } return true; }), }; }); const isWide = useMedia('(min-width: 767.99px)', true); const [drawOpen, setDrawOpen] = useState(false); const menuSider = ( {isWide ? ( {menu} ) : ( ) : ( ) } wrapperClassName={styles.menuDrawer} onChange={(open) => setDrawOpen(!!open)} width={280} > {menu} )} ); type NewDemo = { title: string; id: string; category: string; }; /** 获取上新的 demo. 直接用英文 title 作为 id */ const demosOnTheNew = useMemo((): Array => { const result: NewDemo[] = []; each(allDemosInCategory, (categoryDemos, category) => { const newDemos = filter(categoryDemos, (d) => d.new); // 大于4个新增 demo 或全部新增,则直接使用 category 作为代替 if ( size(newDemos) > 6 || (size(newDemos) && size(newDemos) === size(categoryDemos)) ) { result.push({ title: category, id: getDemoCategory(newDemos[0], 'en'), category, }); } else { each(newDemos, (demo) => result.push({ title: demo.title[i18n.language], id: demo.title.en, category: getDemoCategory(demo), }), ); } }); return result; }, [allDemosInCategory, allDemos, i18n.language]); /** 公告 id */ const bannerId = useMemo(() => { return demosOnTheNew.map((d) => d.id).join('-'); }, [demosOnTheNew]); const galleryPageContent = (
{/* 是否展示上新公告 */} {demosOnTheNew.length > 0 ? ( {t('上新啦,点击直达:')} {demosOnTheNew.map((demo, idx) => ( {idx !== 0 && ','} {demo.title} ))}
} localStorageId={BANNER_LOCALSTORAGE_KEY} bannerId={bannerId} /> ) : null} {Categories.map((category: string, i) => (
{category !== 'OTHER' && (

{category}

)}
    {allDemosInCategory[category] .sort((a, b) => { return (a.order || -1) - (b.order || -1); }) .map((demo) => { let cardTitle; if (typeof demo.title === 'string') { cardTitle = demo.title; } else { cardTitle = demo.title ? demo.title[i18n.language] : demo.filename; } const demoSlug = demo.relativePath.replace( /\/demo\/(.*)\..*/, (_: string, filename: string) => { return `#${filename}`; }, ); const card = (
    {cardTitle}
    ); return (
  • {demo.new ? ( {card} ) : ( card )}

    {cardTitle}

  • ); })}
))}
); const exmaplePageContent = (
{exampleSections.examples && exampleSections.examples.length > 0 && ( )}
); // 判断当前所在页面 useEffect(() => { if (pathWithoutTrailingSlashes.endsWith('/examples/gallery')) { gallery = true; } else { gallery = false; } return () => { document.getElementsByTagName('body')[0].style.overflow = 'auto'; }; }, [pathWithoutTrailingSlashes]); return ( <> {gallery ? menuSider : null} {gallery ? (
{galleryPageContent}
) : (
{exmaplePageContent}
)}
); } export const pageQuery = graphql` query { site { siteMetadata { title galleryMenuCloseAll examples { slug icon title { zh en } } playground { container playgroundDidMount playgroundWillUnmount dependencies devDependencies htmlCodeTemplate } } pathPrefix } allMarkdownRemark( filter: { fields: { slug: { regex: "//examples//" } } } sort: { order: ASC, fields: [frontmatter___order] } limit: 1000 ) { edges { node { fields { slug } frontmatter { title order icon } parent { ... on File { relativePath } } } } } } `;