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)