Commit cd042254 authored by JzoNg's avatar JzoNg

feat: support notion page preview

parent 1ee00cba
...@@ -11,6 +11,9 @@ ...@@ -11,6 +11,9 @@
} }
.previewHeader .title { .previewHeader .title {
display: flex;
justify-content: space-between;
align-items: center;
color: #101828; color: #101828;
font-weight: 600; font-weight: 600;
font-size: 18px; font-size: 18px;
......
...@@ -2,12 +2,14 @@ ...@@ -2,12 +2,14 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import cn from 'classnames' import cn from 'classnames'
import { XMarkIcon } from '@heroicons/react/20/solid'
import s from './index.module.css' import s from './index.module.css'
import type { File } from '@/models/datasets' import type { File } from '@/models/datasets'
import { fetchFilePreview } from '@/service/common' import { fetchFilePreview } from '@/service/common'
type IProps = { type IProps = {
file?: File file?: File
notionPage?: any
hidePreview: () => void hidePreview: () => void
} }
...@@ -46,9 +48,9 @@ const FilePreview = ({ ...@@ -46,9 +48,9 @@ const FilePreview = ({
<div className={cn(s.previewHeader)}> <div className={cn(s.previewHeader)}>
<div className={cn(s.title)}> <div className={cn(s.title)}>
<span>{t('datasetCreation.stepOne.filePreview')}</span> <span>{t('datasetCreation.stepOne.filePreview')}</span>
{false && ( <div className='flex items-center justify-center w-6 h-6 cursor-pointer' onClick={hidePreview}>
<div className='flex items-center justify-center w-6 h-6 cursor-pointer' onClick={hidePreview}></div> <XMarkIcon className='h-4 w-4'></XMarkIcon>
)} </div>
</div> </div>
<div className={cn(s.fileName)}> <div className={cn(s.fileName)}>
<span>{getFileName(file)}</span><span className={cn(s.filetype)}>.{file?.extension}</span> <span>{getFileName(file)}</span><span className={cn(s.filetype)}>.{file?.extension}</span>
......
.filePreview {
@apply flex flex-col border-l border-gray-200 shrink-0;
width: 528px;
background-color: #fcfcfd;
}
.previewHeader {
@apply border-b border-gray-200 shrink-0;
margin: 42px 32px 0;
padding-bottom: 16px;
}
.previewHeader .title {
display: flex;
justify-content: space-between;
align-items: center;
color: #101828;
font-weight: 600;
font-size: 18px;
line-height: 28px;
}
.previewHeader .fileName {
display: flex;
align-items: center;
font-weight: 400;
font-size: 12px;
line-height: 18px;
color: #1D2939;
}
.previewHeader .filetype {
color: #667085;
}
.previewContent {
@apply overflow-y-auto grow;
padding: 20px 32px;
font-weight: 400;
font-size: 16px;
line-height: 24px;
color: #344054;
}
.previewContent .loading {
width: 100%;
height: 180px;
background: #f9fafb center no-repeat url(../assets/Loading.svg);
background-size: contain;
}
.fileContent {
white-space: pre-line;
}
\ No newline at end of file
'use client'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import { XMarkIcon } from '@heroicons/react/20/solid'
import s from './index.module.css'
import type { DataSourceNotionPage } from '@/models/common'
import NotionIcon from '@/app/components/base/notion-icon'
import { fetchNotionPagePreview } from '@/service/datasets'
type Page = DataSourceNotionPage & { workspace_id: string }
type IProps = {
currentPage?: Page
hidePreview: () => void
}
const NotionPagePreview = ({
currentPage,
hidePreview,
}: IProps) => {
const { t } = useTranslation()
const [previewContent, setPreviewContent] = useState('')
const [loading, setLoading] = useState(true)
const getPreviewContent = async () => {
if (!currentPage)
return
try {
const res = await fetchNotionPagePreview({
workspaceID: currentPage.workspace_id,
pageID: currentPage.page_id,
})
setPreviewContent(res.content)
setLoading(false)
}
catch {}
}
const getIcon = () => {
let iconSrc
if (currentPage) {
if (currentPage.page_icon && currentPage.page_icon.type === 'url')
iconSrc = currentPage.page_icon.url
if (currentPage.page_icon && currentPage.page_icon.type === 'emoji')
iconSrc = currentPage.page_icon.emoji
}
return iconSrc
}
useEffect(() => {
if (currentPage) {
setLoading(true)
getPreviewContent()
}
}, [currentPage])
return (
<div className={cn(s.filePreview)}>
<div className={cn(s.previewHeader)}>
<div className={cn(s.title)}>
<span>{t('datasetCreation.stepOne.pagePreview')}</span>
<div className='flex items-center justify-center w-6 h-6 cursor-pointer' onClick={hidePreview}>
<XMarkIcon className='h-4 w-4'></XMarkIcon>
</div>
</div>
<div className={cn(s.fileName)}>
<NotionIcon
className='shrink-0 mr-1'
type='page'
src={getIcon()}
/>
{currentPage?.page_name}
</div>
</div>
<div className={cn(s.previewContent)}>
{loading && <div className={cn(s.loading)}/>}
{!loading && (
<div className={cn(s.fileContent)}>{previewContent}</div>
)}
</div>
</div>
)
}
export default NotionPagePreview
...@@ -4,9 +4,11 @@ import { useTranslation } from 'react-i18next' ...@@ -4,9 +4,11 @@ import { useTranslation } from 'react-i18next'
import cn from 'classnames' import cn from 'classnames'
import FilePreview from '../file-preview' import FilePreview from '../file-preview'
import FileUploader from '../file-uploader' import FileUploader from '../file-uploader'
import NotionPagePreview from '../notion-page-preview'
import EmptyDatasetCreationModal from '../empty-dataset-creation-modal' import EmptyDatasetCreationModal from '../empty-dataset-creation-modal'
import s from './index.module.css' import s from './index.module.css'
import type { File } from '@/models/datasets' import type { File } from '@/models/datasets'
import type { DataSourceNotionPage } from '@/models/common'
import { DataSourceType } from '@/models/datasets' import { DataSourceType } from '@/models/datasets'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { NotionPageSelector } from '@/app/components/base/notion-page-selector' import { NotionPageSelector } from '@/app/components/base/notion-page-selector'
...@@ -24,6 +26,8 @@ type IStepOneProps = { ...@@ -24,6 +26,8 @@ type IStepOneProps = {
changeType: (type: DataSourceType) => void changeType: (type: DataSourceType) => void
} }
type Page = DataSourceNotionPage & { workspace_id: string }
const StepOne = ({ const StepOne = ({
datasetId, datasetId,
dataSourceType, dataSourceType,
...@@ -34,9 +38,11 @@ const StepOne = ({ ...@@ -34,9 +38,11 @@ const StepOne = ({
file, file,
updateFile, updateFile,
notionPages = [], notionPages = [],
updateNotionPages,
}: IStepOneProps) => { }: IStepOneProps) => {
const [showModal, setShowModal] = useState(false) const [showModal, setShowModal] = useState(false)
const [showFilePreview, setShowFilePreview] = useState(true) const [showFilePreview, setShowFilePreview] = useState(true)
const [currentNotionPage, setCurrentNotionPage] = useState<Page | undefined>()
const { t } = useTranslation() const { t } = useTranslation()
const hidePreview = () => setShowFilePreview(false) const hidePreview = () => setShowFilePreview(false)
...@@ -45,6 +51,14 @@ const StepOne = ({ ...@@ -45,6 +51,14 @@ const StepOne = ({
const modalCloseHandle = () => setShowModal(false) const modalCloseHandle = () => setShowModal(false)
const updateCurrentPage = (page: Page) => {
setCurrentNotionPage(page)
}
const hideNotionPagePreview = () => {
setCurrentNotionPage(undefined)
}
return ( return (
<div className='flex w-full h-full'> <div className='flex w-full h-full'>
<div className='grow overflow-y-auto relative'> <div className='grow overflow-y-auto relative'>
...@@ -53,14 +67,20 @@ const StepOne = ({ ...@@ -53,14 +67,20 @@ const StepOne = ({
<div className={s.dataSourceTypeList}> <div className={s.dataSourceTypeList}>
<div <div
className={cn(s.dataSourceItem, dataSourceType === DataSourceType.FILE && s.active)} className={cn(s.dataSourceItem, dataSourceType === DataSourceType.FILE && s.active)}
onClick={() => changeType(DataSourceType.FILE)} onClick={() => {
changeType(DataSourceType.FILE)
hidePreview()
}}
> >
<span className={cn(s.datasetIcon)} /> <span className={cn(s.datasetIcon)} />
{t('datasetCreation.stepOne.dataSourceType.file')} {t('datasetCreation.stepOne.dataSourceType.file')}
</div> </div>
<div <div
className={cn(s.dataSourceItem, dataSourceType === DataSourceType.NOTION && s.active)} className={cn(s.dataSourceItem, dataSourceType === DataSourceType.NOTION && s.active)}
onClick={() => changeType(DataSourceType.NOTION)} onClick={() => {
changeType(DataSourceType.NOTION)
hidePreview()
}}
> >
<span className={cn(s.datasetIcon, s.notion)} /> <span className={cn(s.datasetIcon, s.notion)} />
{t('datasetCreation.stepOne.dataSourceType.notion')} {t('datasetCreation.stepOne.dataSourceType.notion')}
...@@ -85,16 +105,16 @@ const StepOne = ({ ...@@ -85,16 +105,16 @@ const StepOne = ({
{!hasConnection && ( {!hasConnection && (
<div className={s.notionConnectionTip}> <div className={s.notionConnectionTip}>
<span className={s.notionIcon}/> <span className={s.notionIcon}/>
<div className={s.title}>Notion is not connected</div> <div className={s.title}>{t('datasetCreation.stepOne.notionSyncTitle')}</div>
<div className={s.tip}>To sync with Notion, connection to Notion must be established first.</div> <div className={s.tip}>{t('datasetCreation.stepOne.notionSyncTip')}</div>
<Button className='h-8' type='primary' onClick={onSetting}>Go to connect</Button> <Button className='h-8' type='primary' onClick={onSetting}>{t('datasetCreation.stepOne.connect')}</Button>
</div> </div>
)} )}
{hasConnection && ( {hasConnection && (
<> <>
{/* TODO */} {/* TODO */}
<div className='mb-8 w-[640px]'> <div className='mb-8 w-[640px]'>
<NotionPageSelector /> <NotionPageSelector onSelect={updateNotionPages} onPreview={updateCurrentPage} />
</div> </div>
<Button disabled={!notionPages.length} className={s.submitButton} type='primary' onClick={onStepChange}>{t('datasetCreation.stepOne.button')}</Button> <Button disabled={!notionPages.length} className={s.submitButton} type='primary' onClick={onStepChange}>{t('datasetCreation.stepOne.button')}</Button>
</> </>
...@@ -111,7 +131,7 @@ const StepOne = ({ ...@@ -111,7 +131,7 @@ const StepOne = ({
<EmptyDatasetCreationModal show={showModal} onHide={modalCloseHandle} /> <EmptyDatasetCreationModal show={showModal} onHide={modalCloseHandle} />
</div> </div>
{file && showFilePreview && <FilePreview file={file} hidePreview={hidePreview} />} {file && showFilePreview && <FilePreview file={file} hidePreview={hidePreview} />}
{/* TODO notion page preview */} {currentNotionPage && <NotionPagePreview currentPage={currentNotionPage} hidePreview={hideNotionPagePreview} />}
</div> </div>
) )
} }
......
...@@ -13,6 +13,7 @@ const translation = { ...@@ -13,6 +13,7 @@ const translation = {
}, },
stepOne: { stepOne: {
filePreview: 'File Preview', filePreview: 'File Preview',
pagePreview: 'Page Preview',
dataSourceType: { dataSourceType: {
file: 'Import from text file', file: 'Import from text file',
notion: 'Sync from Notion', notion: 'Sync from Notion',
...@@ -32,6 +33,9 @@ const translation = { ...@@ -32,6 +33,9 @@ const translation = {
change: 'Change', change: 'Change',
failed: 'Upload failed', failed: 'Upload failed',
}, },
notionSyncTitle: 'Notion is not connected',
notionSyncTip: 'To sync with Notion, connection to Notion must be established first.',
connect: 'Go to connect',
button: 'next', button: 'next',
emptyDatasetCreation: 'I want to create an empty dataset', emptyDatasetCreation: 'I want to create an empty dataset',
modal: { modal: {
......
...@@ -13,6 +13,7 @@ const translation = { ...@@ -13,6 +13,7 @@ const translation = {
}, },
stepOne: { stepOne: {
filePreview: '文件预览', filePreview: '文件预览',
pagePreview: '页面预览',
dataSourceType: { dataSourceType: {
file: '导入已有文本', file: '导入已有文本',
notion: '同步自 Notion 内容', notion: '同步自 Notion 内容',
...@@ -32,6 +33,9 @@ const translation = { ...@@ -32,6 +33,9 @@ const translation = {
change: '更改文件', change: '更改文件',
failed: '上传失败', failed: '上传失败',
}, },
notionSyncTitle: 'Notion 未绑定',
notionSyncTip: '同步 Notion 内容前,须先绑定 Notion 空间',
connect: '去绑定',
button: '下一步', button: '下一步',
emptyDatasetCreation: '创建一个空数据集', emptyDatasetCreation: '创建一个空数据集',
modal: { modal: {
......
...@@ -125,3 +125,7 @@ export const fetchTestingRecords: Fetcher<HitTestingRecordsResponse, { datasetId ...@@ -125,3 +125,7 @@ export const fetchTestingRecords: Fetcher<HitTestingRecordsResponse, { datasetId
export const fetchFileIndexingEstimate: Fetcher<FileIndexingEstimateResponse, any> = (body: any) => { export const fetchFileIndexingEstimate: Fetcher<FileIndexingEstimateResponse, any> = (body: any) => {
return post('/datasets/indexing-estimate', { body }) as Promise<FileIndexingEstimateResponse> return post('/datasets/indexing-estimate', { body }) as Promise<FileIndexingEstimateResponse>
} }
export const fetchNotionPagePreview: Fetcher<{ content: string }, { workspaceID: string; pageID: string }> = ({ workspaceID, pageID }) => {
return get(`notion/workspaces/${workspaceID}/pages/${pageID}/preview`) as Promise<{ content: string }>
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment