condition-item.tsx 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useCallback, useEffect } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import cn from 'classnames'
  6. import {
  7. RiDeleteBinLine,
  8. } from '@remixicon/react'
  9. import VarReferencePicker from '../../_base/components/variable/var-reference-picker'
  10. import { isComparisonOperatorNeedTranslate } from '../utils'
  11. import { VarType } from '../../../types'
  12. import type { Condition } from '@/app/components/workflow/nodes/if-else/types'
  13. import { ComparisonOperator, LogicalOperator } from '@/app/components/workflow/nodes/if-else/types'
  14. import type { ValueSelector, Var } from '@/app/components/workflow/types'
  15. import { RefreshCw05 } from '@/app/components/base/icons/src/vender/line/arrows'
  16. import Selector from '@/app/components/workflow/nodes/_base/components/selector'
  17. import Toast from '@/app/components/base/toast'
  18. const i18nPrefix = 'workflow.nodes.ifElse'
  19. const Line = (
  20. <svg xmlns="http://www.w3.org/2000/svg" width="163" height="2" viewBox="0 0 163 2" fill="none">
  21. <path d="M0 1H162.5" stroke="url(#paint0_linear_641_36452)" />
  22. <defs>
  23. <linearGradient id="paint0_linear_641_36452" x1="162.5" y1="9.99584" x2="6.6086e-06" y2="9.94317" gradientUnits="userSpaceOnUse">
  24. <stop stopColor="#F3F4F6" />
  25. <stop offset="1" stopColor="#F3F4F6" stopOpacity="0" />
  26. </linearGradient>
  27. </defs>
  28. </svg>
  29. )
  30. const getOperators = (type?: VarType) => {
  31. switch (type) {
  32. case VarType.string:
  33. return [
  34. ComparisonOperator.contains,
  35. ComparisonOperator.notContains,
  36. ComparisonOperator.startWith,
  37. ComparisonOperator.endWith,
  38. ComparisonOperator.is,
  39. ComparisonOperator.isNot,
  40. ComparisonOperator.empty,
  41. ComparisonOperator.notEmpty,
  42. ]
  43. case VarType.number:
  44. return [
  45. ComparisonOperator.equal,
  46. ComparisonOperator.notEqual,
  47. ComparisonOperator.largerThan,
  48. ComparisonOperator.lessThan,
  49. ComparisonOperator.largerThanOrEqual,
  50. ComparisonOperator.lessThanOrEqual,
  51. ComparisonOperator.is,
  52. ComparisonOperator.isNot,
  53. ComparisonOperator.empty,
  54. ComparisonOperator.notEmpty,
  55. ]
  56. case VarType.arrayString:
  57. case VarType.arrayNumber:
  58. return [
  59. ComparisonOperator.contains,
  60. ComparisonOperator.notContains,
  61. ComparisonOperator.empty,
  62. ComparisonOperator.notEmpty,
  63. ]
  64. case VarType.array:
  65. case VarType.arrayObject:
  66. return [
  67. ComparisonOperator.empty,
  68. ComparisonOperator.notEmpty,
  69. ]
  70. default:
  71. return [
  72. ComparisonOperator.is,
  73. ComparisonOperator.isNot,
  74. ComparisonOperator.empty,
  75. ComparisonOperator.notEmpty,
  76. ]
  77. }
  78. }
  79. type ItemProps = {
  80. readonly: boolean
  81. nodeId: string
  82. payload: Condition
  83. varType?: VarType
  84. onChange: (newItem: Condition) => void
  85. canRemove: boolean
  86. onRemove?: () => void
  87. isShowLogicalOperator?: boolean
  88. logicalOperator: LogicalOperator
  89. onLogicalOperatorToggle: () => void
  90. filterVar: (varPayload: Var) => boolean
  91. }
  92. const Item: FC<ItemProps> = ({
  93. readonly,
  94. nodeId,
  95. payload,
  96. varType = VarType.string,
  97. onChange,
  98. canRemove,
  99. onRemove = () => { },
  100. isShowLogicalOperator,
  101. logicalOperator,
  102. onLogicalOperatorToggle,
  103. filterVar,
  104. }) => {
  105. const { t } = useTranslation()
  106. const isValueReadOnly = payload.comparison_operator ? [ComparisonOperator.empty, ComparisonOperator.notEmpty, ComparisonOperator.isNull, ComparisonOperator.isNotNull].includes(payload.comparison_operator) : false
  107. const handleVarReferenceChange = useCallback((value: ValueSelector | string) => {
  108. onChange({
  109. ...payload,
  110. variable_selector: value as ValueSelector,
  111. })
  112. }, [onChange, payload])
  113. // change to default operator if the variable type is changed
  114. useEffect(() => {
  115. if (varType && payload.comparison_operator) {
  116. if (!getOperators(varType).includes(payload.comparison_operator)) {
  117. onChange({
  118. ...payload,
  119. comparison_operator: getOperators(varType)[0],
  120. })
  121. }
  122. }
  123. // eslint-disable-next-line react-hooks/exhaustive-deps
  124. }, [varType, payload])
  125. const handleValueChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
  126. onChange({
  127. ...payload,
  128. value: e.target.value,
  129. })
  130. }, [onChange, payload])
  131. const handleComparisonOperatorChange = useCallback((v: ComparisonOperator) => {
  132. onChange({
  133. ...payload,
  134. comparison_operator: v,
  135. })
  136. }, [onChange, payload])
  137. return (
  138. <div className='space-y-2'>
  139. {isShowLogicalOperator && (
  140. <div className='flex items-center justify-center select-none'>
  141. <div className='flex items-center '>
  142. {Line}
  143. <div
  144. className='shrink-0 mx-1 flex items-center h-[22px] pl-2 pr-1.5 border border-gray-200 rounded-lg bg-white shadow-xs space-x-0.5 text-primary-600 cursor-pointer'
  145. onClick={onLogicalOperatorToggle}
  146. >
  147. <div className='text-xs font-semibold uppercase'>{t(`${i18nPrefix}.${logicalOperator === LogicalOperator.and ? 'and' : 'or'}`)}</div>
  148. <RefreshCw05 className='w-3 h-3' />
  149. </div>
  150. <div className=' rotate-180'>
  151. {Line}
  152. </div>
  153. </div>
  154. </div>
  155. )
  156. }
  157. <div className='flex items-center space-x-1'>
  158. <VarReferencePicker
  159. nodeId={nodeId}
  160. readonly={readonly}
  161. isShowNodeName
  162. className='min-w-[162px] flex-grow'
  163. value={payload.variable_selector}
  164. onChange={handleVarReferenceChange}
  165. filterVar={filterVar}
  166. />
  167. <Selector
  168. popupClassName='top-[34px]'
  169. itemClassName='capitalize'
  170. trigger={
  171. <div
  172. onClick={(e) => {
  173. if (readonly) {
  174. e.stopPropagation()
  175. return
  176. }
  177. if (!payload.variable_selector || payload.variable_selector.length === 0) {
  178. e.stopPropagation()
  179. Toast.notify({
  180. message: t(`${i18nPrefix}.notSetVariable`),
  181. type: 'error',
  182. })
  183. }
  184. }}
  185. className={cn(!readonly && 'cursor-pointer', 'shrink-0 w-[100px] whitespace-nowrap flex items-center h-8 justify-between px-2.5 rounded-lg bg-gray-100 capitalize')}
  186. >
  187. {
  188. !payload.comparison_operator
  189. ? <div className='text-[13px] font-normal text-gray-400'>{t(`${i18nPrefix}.operator`)}</div>
  190. : <div className='text-[13px] font-normal text-gray-900'>{isComparisonOperatorNeedTranslate(payload.comparison_operator) ? t(`${i18nPrefix}.comparisonOperator.${payload.comparison_operator}`) : payload.comparison_operator}</div>
  191. }
  192. </div>
  193. }
  194. readonly={readonly}
  195. value={payload.comparison_operator || ''}
  196. options={getOperators(varType).map((o) => {
  197. return {
  198. label: isComparisonOperatorNeedTranslate(o) ? t(`${i18nPrefix}.comparisonOperator.${o}`) : o,
  199. value: o,
  200. }
  201. })}
  202. onChange={handleComparisonOperatorChange}
  203. />
  204. <input
  205. readOnly={readonly || isValueReadOnly || !varType}
  206. onClick={() => {
  207. if (readonly)
  208. return
  209. if (!varType) {
  210. Toast.notify({
  211. message: t(`${i18nPrefix}.notSetVariable`),
  212. type: 'error',
  213. })
  214. }
  215. }}
  216. value={!isValueReadOnly ? payload.value : ''}
  217. onChange={handleValueChange}
  218. placeholder={(!readonly && !isValueReadOnly) ? t(`${i18nPrefix}.enterValue`)! : ''}
  219. className='min-w-[80px] flex-grow h-8 leading-8 px-2.5 rounded-lg border-0 bg-gray-100 text-gray-900 text-[13px] placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset focus:ring-gray-200'
  220. type='text'
  221. />
  222. {!readonly && (
  223. <div
  224. className={cn(canRemove ? 'text-gray-500 bg-gray-100 hover:bg-gray-200 cursor-pointer' : 'bg-gray-25 text-gray-300', 'p-2 rounded-lg ')}
  225. onClick={canRemove ? onRemove : () => { }}
  226. >
  227. <RiDeleteBinLine className='w-4 h-4 ' />
  228. </div>
  229. )}
  230. </div>
  231. </div >
  232. )
  233. }
  234. export default React.memo(Item)