index.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import {
  2. memo,
  3. useCallback,
  4. useRef,
  5. } from 'react'
  6. import { useTranslation } from 'react-i18next'
  7. import { useClickAway } from 'ahooks'
  8. import type { NodeProps } from 'reactflow'
  9. import NodeResizer from '../nodes/_base/components/node-resizer'
  10. import {
  11. useNodeDataUpdate,
  12. useNodesInteractions,
  13. } from '../hooks'
  14. import { useStore } from '../store'
  15. import {
  16. NoteEditor,
  17. NoteEditorContextProvider,
  18. NoteEditorToolbar,
  19. } from './note-editor'
  20. import { THEME_MAP } from './constants'
  21. import { useNote } from './hooks'
  22. import type { NoteNodeType } from './types'
  23. import cn from '@/utils/classnames'
  24. const Icon = () => {
  25. return (
  26. <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
  27. <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" />
  28. </svg>
  29. )
  30. }
  31. const NoteNode = ({
  32. id,
  33. data,
  34. }: NodeProps<NoteNodeType>) => {
  35. const { t } = useTranslation()
  36. const controlPromptEditorRerenderKey = useStore(s => s.controlPromptEditorRerenderKey)
  37. const ref = useRef<HTMLDivElement | null>(null)
  38. const theme = data.theme
  39. const {
  40. handleThemeChange,
  41. handleEditorChange,
  42. handleShowAuthorChange,
  43. } = useNote(id)
  44. const {
  45. handleNodesCopy,
  46. handleNodesDuplicate,
  47. handleNodeDelete,
  48. } = useNodesInteractions()
  49. const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
  50. const handleDeleteNode = useCallback(() => {
  51. handleNodeDelete(id)
  52. }, [id, handleNodeDelete])
  53. useClickAway(() => {
  54. handleNodeDataUpdateWithSyncDraft({ id, data: { selected: false } })
  55. }, ref)
  56. return (
  57. <div
  58. className={cn(
  59. 'flex flex-col relative rounded-md shadow-xs border hover:shadow-md',
  60. THEME_MAP[theme].bg,
  61. data.selected ? THEME_MAP[theme].border : 'border-black/5',
  62. )}
  63. style={{
  64. width: data.width,
  65. height: data.height,
  66. }}
  67. ref={ref}
  68. >
  69. <NoteEditorContextProvider
  70. key={controlPromptEditorRerenderKey}
  71. value={data.text}
  72. >
  73. <>
  74. <NodeResizer
  75. nodeId={id}
  76. nodeData={data}
  77. icon={<Icon />}
  78. minWidth={240}
  79. minHeight={88}
  80. />
  81. <div
  82. className={cn(
  83. 'shrink-0 h-2 opacity-50 rounded-t-md',
  84. THEME_MAP[theme].title,
  85. )}></div>
  86. {
  87. data.selected && (
  88. <div className='absolute top-[-41px] left-1/2 -translate-x-1/2'>
  89. <NoteEditorToolbar
  90. theme={theme}
  91. onThemeChange={handleThemeChange}
  92. onCopy={handleNodesCopy}
  93. onDuplicate={handleNodesDuplicate}
  94. onDelete={handleDeleteNode}
  95. showAuthor={data.showAuthor}
  96. onShowAuthorChange={handleShowAuthorChange}
  97. />
  98. </div>
  99. )
  100. }
  101. <div className='grow px-3 py-2.5 overflow-y-auto'>
  102. <div className={cn(
  103. data.selected && 'nodrag nopan nowheel cursor-text',
  104. )}>
  105. <NoteEditor
  106. containerElement={ref.current}
  107. placeholder={t('workflow.nodes.note.editor.placeholder') || ''}
  108. onChange={handleEditorChange}
  109. />
  110. </div>
  111. </div>
  112. {
  113. data.showAuthor && (
  114. <div className='p-3 pt-0 text-xs text-text-tertiary'>
  115. {data.author}
  116. </div>
  117. )
  118. }
  119. </>
  120. </NoteEditorContextProvider>
  121. </div>
  122. )
  123. }
  124. export default memo(NoteNode)