123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- 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<string>
- disabledValue: Set<string>
- searchValue: string
- pagesMap: DataSourceNotionPageMap
- list: DataSourceNotionPage[]
- onSelect: (selectedPagesId: Set<string>) => void
- canPreview?: boolean
- previewPageId?: string
- onPreview?: (selectedPageId: string) => void
- }
- type NotionPageTreeItem = {
- children: Set<string>
- descendants: Set<string>
- depth: number
- ancestors: string[]
- } & DataSourceNotionPage
- type NotionPageTreeMap = Record<string, NotionPageTreeItem>
- 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<string>
- disabledCheckedIds: Set<string>
- 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 (
- <div
- className={cn(s.arrow, current.expand && s['arrow-expand'], 'shrink-0 mr-1 w-5 h-5 hover:bg-gray-200 rounded-md')}
- style={{ marginLeft: current.depth * 8 }}
- onClick={() => handleToggle(index)}
- />
- )
- }
- if (current.parent_id === 'root' || !pagesMap[current.parent_id]) {
- return (
- <div></div>
- )
- }
- return (
- <div className='shrink-0 mr-1 w-5 h-5' style={{ marginLeft: current.depth * 8 }} />
- )
- }
- return (
- <div
- className={cn('group flex items-center pl-2 pr-[2px] rounded-md border border-transparent hover:bg-gray-100 cursor-pointer', previewPageId === current.page_id && s['preview-item'])}
- style={{ ...style, top: style.top as number + 8, left: 8, right: 8, width: 'calc(100% - 16px)' }}
- >
- <Checkbox
- className={cn(
- 'shrink-0 mr-2 group-hover:border-primary-600 group-hover:border-[2px]',
- disabled && 'group-hover:border-transparent',
- )}
- checked={checkedIds.has(current.page_id)}
- disabled={disabled}
- onCheck={() => {
- if (disabled)
- return
- handleCheck(index)
- }}
- />
- {!searchValue && renderArrow()}
- <NotionIcon
- className='shrink-0 mr-1'
- type='page'
- src={current.page_icon}
- />
- <div
- className='grow text-sm font-medium text-gray-700 truncate'
- title={current.page_name}
- >
- {current.page_name}
- </div>
- {
- canPreview && (
- <div
- className='shrink-0 hidden group-hover:flex items-center ml-1 px-2 h-6 rounded-md text-xs font-medium text-gray-500 cursor-pointer hover:bg-gray-50 hover:text-gray-700'
- onClick={() => handlePreview(index)}>
- {t('common.dataSource.notion.selector.preview')}
- </div>
- )
- }
- {
- searchValue && (
- <div
- className='shrink-0 ml-1 max-w-[120px] text-xs text-gray-400 truncate'
- title={breadCrumbs.join(' / ')}
- >
- {breadCrumbs.join(' / ')}
- </div>
- )
- }
- </div>
- )
- }
- 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<NotionPageItem[]>([])
- 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 (
- <div className='flex items-center justify-center h-[296px] text-[13px] text-gray-500'>
- {t('common.dataSource.notion.selector.noSearchResult')}
- </div>
- )
- }
- return (
- <List
- className='py-2'
- height={296}
- itemCount={currentDataList.length}
- itemSize={28}
- width='100%'
- itemKey={(index, data) => data.dataList[index].page_id}
- itemData={{
- dataList: currentDataList,
- handleToggle,
- checkedIds: value,
- disabledCheckedIds: disabledValue,
- handleCheck,
- canPreview,
- handlePreview,
- listMapWithChildrenAndDescendants,
- searchValue,
- previewPageId: currentPreviewPageId,
- pagesMap,
- }}
- >
- {Item}
- </List>
- )
- }
- export default PageSelector
|