index.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  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. )}
  61. style={{
  62. background: THEME_MAP[theme].bg,
  63. borderColor: data.selected ? THEME_MAP[theme].border : 'rgba(0, 0, 0, 0.05)',
  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 className='shrink-0 h-2 opacity-50 rounded-t-md' style={{ background: THEME_MAP[theme].title }}></div>
  82. {
  83. data.selected && (
  84. <div className='absolute -top-[41px] left-1/2 -translate-x-1/2'>
  85. <NoteEditorToolbar
  86. theme={theme}
  87. onThemeChange={handleThemeChange}
  88. onCopy={handleNodesCopy}
  89. onDuplicate={handleNodesDuplicate}
  90. onDelete={handleDeleteNode}
  91. showAuthor={data.showAuthor}
  92. onShowAuthorChange={handleShowAuthorChange}
  93. />
  94. </div>
  95. )
  96. }
  97. <div className='grow px-3 py-2.5 overflow-y-auto'>
  98. <div className={cn(
  99. data.selected && 'nodrag nopan nowheel cursor-text',
  100. )}>
  101. <NoteEditor
  102. containerElement={ref.current}
  103. placeholder={t('workflow.nodes.note.editor.placeholder') || ''}
  104. onChange={handleEditorChange}
  105. />
  106. </div>
  107. </div>
  108. {
  109. data.showAuthor && (
  110. <div className='p-3 pt-0 text-xs text-black/[0.32]'>
  111. {data.author}
  112. </div>
  113. )
  114. }
  115. </>
  116. </NoteEditorContextProvider>
  117. </div>
  118. )
  119. }
  120. export default memo(NoteNode)