import { memo, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { FixedSizeList as List, areEqual } from 'react-window' import type { ListChildComponentProps } from 'react-window' import Checkbox from '../../checkbox' import NotionIcon from '../../notion-icon' import s from './index.module.css' import cn from '@/utils/classnames' import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common' type PageSelectorProps = { value: Set disabledValue: Set searchValue: string pagesMap: DataSourceNotionPageMap list: DataSourceNotionPage[] onSelect: (selectedPagesId: Set) => void canPreview?: boolean previewPageId?: string onPreview?: (selectedPageId: string) => void } type NotionPageTreeItem = { children: Set descendants: Set depth: number ancestors: string[] } & DataSourceNotionPage type NotionPageTreeMap = Record type NotionPageItem = { expand: boolean depth: number } & DataSourceNotionPage const recursivePushInParentDescendants = ( pagesMap: DataSourceNotionPageMap, listTreeMap: NotionPageTreeMap, current: NotionPageTreeItem, leafItem: NotionPageTreeItem, ) => { const parentId = current.parent_id const pageId = current.page_id if (!parentId || !pageId) return if (parentId !== 'root' && pagesMap[parentId]) { if (!listTreeMap[parentId]) { const children = new Set([pageId]) const descendants = new Set([pageId, leafItem.page_id]) listTreeMap[parentId] = { ...pagesMap[parentId], children, descendants, depth: 0, ancestors: [], } } else { listTreeMap[parentId].children.add(pageId) listTreeMap[parentId].descendants.add(pageId) listTreeMap[parentId].descendants.add(leafItem.page_id) } leafItem.depth++ leafItem.ancestors.unshift(listTreeMap[parentId].page_name) if (listTreeMap[parentId].parent_id !== 'root') recursivePushInParentDescendants(pagesMap, listTreeMap, listTreeMap[parentId], leafItem) } } const ItemComponent = ({ index, style, data }: ListChildComponentProps<{ dataList: NotionPageItem[] handleToggle: (index: number) => void checkedIds: Set disabledCheckedIds: Set handleCheck: (index: number) => void canPreview?: boolean handlePreview: (index: number) => void listMapWithChildrenAndDescendants: NotionPageTreeMap searchValue: string previewPageId: string pagesMap: DataSourceNotionPageMap }>) => { const { t } = useTranslation() const { dataList, handleToggle, checkedIds, disabledCheckedIds, handleCheck, canPreview, handlePreview, listMapWithChildrenAndDescendants, searchValue, previewPageId, pagesMap } = data const current = dataList[index] const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[current.page_id] const hasChild = currentWithChildrenAndDescendants.descendants.size > 0 const ancestors = currentWithChildrenAndDescendants.ancestors const breadCrumbs = ancestors.length ? [...ancestors, current.page_name] : [current.page_name] const disabled = disabledCheckedIds.has(current.page_id) const renderArrow = () => { if (hasChild) { return (
handleToggle(index)} /> ) } if (current.parent_id === 'root' || !pagesMap[current.parent_id]) { return (
) } return (
) } return (
{ if (disabled) return handleCheck(index) }} /> {!searchValue && renderArrow()}
{current.page_name}
{ canPreview && (
handlePreview(index)}> {t('common.dataSource.notion.selector.preview')}
) } { searchValue && (
{breadCrumbs.join(' / ')}
) }
) } const Item = memo(ItemComponent, areEqual) const PageSelector = ({ value, disabledValue, searchValue, pagesMap, list, onSelect, canPreview = true, previewPageId, onPreview, }: PageSelectorProps) => { const { t } = useTranslation() const [prevDataList, setPrevDataList] = useState(list) const [dataList, setDataList] = useState([]) const [localPreviewPageId, setLocalPreviewPageId] = useState('') if (prevDataList !== list) { setPrevDataList(list) setDataList(list.filter(item => item.parent_id === 'root' || !pagesMap[item.parent_id]).map((item) => { return { ...item, expand: false, depth: 0, } })) } const searchDataList = list.filter((item) => { return item.page_name.includes(searchValue) }).map((item) => { return { ...item, expand: false, depth: 0, } }) const currentDataList = searchValue ? searchDataList : dataList const currentPreviewPageId = previewPageId === undefined ? localPreviewPageId : previewPageId const listMapWithChildrenAndDescendants = useMemo(() => { return list.reduce((prev: NotionPageTreeMap, next: DataSourceNotionPage) => { const pageId = next.page_id if (!prev[pageId]) prev[pageId] = { ...next, children: new Set(), descendants: new Set(), depth: 0, ancestors: [] } recursivePushInParentDescendants(pagesMap, prev, prev[pageId], prev[pageId]) return prev }, {}) }, [list, pagesMap]) const handleToggle = (index: number) => { const current = dataList[index] const pageId = current.page_id const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[pageId] const descendantsIds = Array.from(currentWithChildrenAndDescendants.descendants) const childrenIds = Array.from(currentWithChildrenAndDescendants.children) let newDataList = [] if (current.expand) { current.expand = false newDataList = [...dataList.filter(item => !descendantsIds.includes(item.page_id))] } else { current.expand = true newDataList = [ ...dataList.slice(0, index + 1), ...childrenIds.map(item => ({ ...pagesMap[item], expand: false, depth: listMapWithChildrenAndDescendants[item].depth, })), ...dataList.slice(index + 1)] } setDataList(newDataList) } const copyValue = new Set([...value]) const handleCheck = (index: number) => { const current = currentDataList[index] const pageId = current.page_id const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[pageId] if (copyValue.has(pageId)) { if (!searchValue) { for (const item of currentWithChildrenAndDescendants.descendants) copyValue.delete(item) } copyValue.delete(pageId) } else { if (!searchValue) { for (const item of currentWithChildrenAndDescendants.descendants) copyValue.add(item) } copyValue.add(pageId) } onSelect(new Set([...copyValue])) } const handlePreview = (index: number) => { const current = currentDataList[index] const pageId = current.page_id setLocalPreviewPageId(pageId) if (onPreview) onPreview(pageId) } if (!currentDataList.length) { return (
{t('common.dataSource.notion.selector.noSearchResult')}
) } return ( data.dataList[index].page_id} itemData={{ dataList: currentDataList, handleToggle, checkedIds: value, disabledCheckedIds: disabledValue, handleCheck, canPreview, handlePreview, listMapWithChildrenAndDescendants, searchValue, previewPageId: currentPreviewPageId, pagesMap, }} > {Item} ) } export default PageSelector