| import { | |
| memo, | |
| useCallback, | |
| useRef, | |
| } from 'react' | |
| import { useTranslation } from 'react-i18next' | |
| import { useClickAway } from 'ahooks' | |
| import type { NodeProps } from 'reactflow' | |
| import NodeResizer from '../nodes/_base/components/node-resizer' | |
| import { | |
| useNodeDataUpdate, | |
| useNodesInteractions, | |
| } from '../hooks' | |
| import { useStore } from '../store' | |
| import { | |
| NoteEditor, | |
| NoteEditorContextProvider, | |
| NoteEditorToolbar, | |
| } from './note-editor' | |
| import { THEME_MAP } from './constants' | |
| import { useNote } from './hooks' | |
| import type { NoteNodeType } from './types' | |
| import cn from '@/utils/classnames' | |
| const Icon = () => { | |
| return ( | |
| <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none"> | |
| <path fillRule="evenodd" clipRule="evenodd" d="M12 9.75V6H13.5V9.75C13.5 11.8211 11.8211 13.5 9.75 13.5H6V12H9.75C10.9926 12 12 10.9926 12 9.75Z" fill="black" fillOpacity="0.16" /> | |
| </svg> | |
| ) | |
| } | |
| const NoteNode = ({ | |
| id, | |
| data, | |
| }: NodeProps<NoteNodeType>) => { | |
| const { t } = useTranslation() | |
| const controlPromptEditorRerenderKey = useStore(s => s.controlPromptEditorRerenderKey) | |
| const ref = useRef<HTMLDivElement | null>(null) | |
| const theme = data.theme | |
| const { | |
| handleThemeChange, | |
| handleEditorChange, | |
| handleShowAuthorChange, | |
| } = useNote(id) | |
| const { | |
| handleNodesCopy, | |
| handleNodesDuplicate, | |
| handleNodeDelete, | |
| } = useNodesInteractions() | |
| const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate() | |
| const handleDeleteNode = useCallback(() => { | |
| handleNodeDelete(id) | |
| }, [id, handleNodeDelete]) | |
| useClickAway(() => { | |
| handleNodeDataUpdateWithSyncDraft({ id, data: { selected: false } }) | |
| }, ref) | |
| return ( | |
| <div | |
| className={cn( | |
| 'flex flex-col relative rounded-md shadow-xs border hover:shadow-md', | |
| )} | |
| style={{ | |
| background: THEME_MAP[theme].bg, | |
| borderColor: data.selected ? THEME_MAP[theme].border : 'rgba(0, 0, 0, 0.05)', | |
| width: data.width, | |
| height: data.height, | |
| }} | |
| ref={ref} | |
| > | |
| <NoteEditorContextProvider | |
| key={controlPromptEditorRerenderKey} | |
| value={data.text} | |
| > | |
| <> | |
| <NodeResizer | |
| nodeId={id} | |
| nodeData={data} | |
| icon={<Icon />} | |
| minWidth={240} | |
| maxWidth={640} | |
| minHeight={88} | |
| /> | |
| <div className='shrink-0 h-2 opacity-50 rounded-t-md' style={{ background: THEME_MAP[theme].title }}></div> | |
| { | |
| data.selected && ( | |
| <div className='absolute -top-[41px] left-1/2 -translate-x-1/2'> | |
| <NoteEditorToolbar | |
| theme={theme} | |
| onThemeChange={handleThemeChange} | |
| onCopy={handleNodesCopy} | |
| onDuplicate={handleNodesDuplicate} | |
| onDelete={handleDeleteNode} | |
| showAuthor={data.showAuthor} | |
| onShowAuthorChange={handleShowAuthorChange} | |
| /> | |
| </div> | |
| ) | |
| } | |
| <div className='grow px-3 py-2.5 overflow-y-auto'> | |
| <div className={cn( | |
| data.selected && 'nodrag nopan nowheel cursor-text', | |
| )}> | |
| <NoteEditor | |
| containerElement={ref.current} | |
| placeholder={t('workflow.nodes.note.editor.placeholder') || ''} | |
| onChange={handleEditorChange} | |
| /> | |
| </div> | |
| </div> | |
| { | |
| data.showAuthor && ( | |
| <div className='p-3 pt-0 text-xs text-black/[0.32]'> | |
| {data.author} | |
| </div> | |
| ) | |
| } | |
| </> | |
| </NoteEditorContextProvider> | |
| </div> | |
| ) | |
| } | |
| export default memo(NoteNode) | |