version-history-item.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import React, { useEffect, useState } from 'react'
  2. import dayjs from 'dayjs'
  3. import { useTranslation } from 'react-i18next'
  4. import ContextMenu from './context-menu'
  5. import cn from '@/utils/classnames'
  6. import type { VersionHistory } from '@/types/workflow'
  7. import { type VersionHistoryContextMenuOptions, WorkflowVersion } from '../../types'
  8. type VersionHistoryItemProps = {
  9. item: VersionHistory
  10. currentVersion: VersionHistory | null
  11. latestVersionId: string
  12. onClick: (item: VersionHistory) => void
  13. handleClickMenuItem: (operation: VersionHistoryContextMenuOptions) => void
  14. isLast: boolean
  15. }
  16. const formatVersion = (versionHistory: VersionHistory, latestVersionId: string): string => {
  17. const { version, id } = versionHistory
  18. if (version === WorkflowVersion.Draft)
  19. return WorkflowVersion.Draft
  20. if (id === latestVersionId)
  21. return WorkflowVersion.Latest
  22. try {
  23. const date = new Date(version)
  24. if (Number.isNaN(date.getTime()))
  25. return version
  26. // format as YYYY-MM-DD HH:mm:ss
  27. return date.toISOString().slice(0, 19).replace('T', ' ')
  28. }
  29. catch {
  30. return version
  31. }
  32. }
  33. const VersionHistoryItem: React.FC<VersionHistoryItemProps> = ({
  34. item,
  35. currentVersion,
  36. latestVersionId,
  37. onClick,
  38. handleClickMenuItem,
  39. isLast,
  40. }) => {
  41. const { t } = useTranslation()
  42. const [isHovering, setIsHovering] = useState(false)
  43. const [open, setOpen] = useState(false)
  44. const formatTime = (time: number) => dayjs.unix(time).format('YYYY-MM-DD HH:mm')
  45. const formattedVersion = formatVersion(item, latestVersionId)
  46. const isSelected = item.version === currentVersion?.version
  47. const isDraft = formattedVersion === WorkflowVersion.Draft
  48. const isLatest = formattedVersion === WorkflowVersion.Latest
  49. useEffect(() => {
  50. if (isDraft)
  51. onClick(item)
  52. // eslint-disable-next-line react-hooks/exhaustive-deps
  53. }, [])
  54. const handleClickItem = () => {
  55. if (isSelected)
  56. return
  57. onClick(item)
  58. }
  59. return (
  60. <div
  61. className={cn(
  62. 'flex gap-x-1 relative p-2 rounded-lg group',
  63. isSelected ? 'bg-state-accent-active cursor-not-allowed' : 'hover:bg-state-base-hover cursor-pointer',
  64. )}
  65. onClick={handleClickItem}
  66. onMouseEnter={() => setIsHovering(true)}
  67. onMouseLeave={() => {
  68. setIsHovering(false)
  69. setOpen(false)
  70. }}
  71. onContextMenu={(e) => {
  72. e.preventDefault()
  73. setOpen(true)
  74. }}
  75. >
  76. {!isLast && <div className='absolute w-0.5 h-[calc(100%-0.75rem)] left-4 top-6 bg-divider-subtle' />}
  77. <div className=' flex items-center justify-center shrink-0 w-[18px] h-5'>
  78. <div className={cn(
  79. 'w-2 h-2 border-[2px] rounded-lg',
  80. isSelected ? 'border-text-accent' : 'border-text-quaternary',
  81. )}/>
  82. </div>
  83. <div className='flex flex-col gap-y-0.5 grow overflow-hidden'>
  84. <div className='flex items-center gap-x-1 h-5 mr-6'>
  85. <div className={cn(
  86. 'py-[1px] system-sm-semibold truncate',
  87. isSelected ? 'text-text-accent' : 'text-text-secondary',
  88. )}>
  89. {isDraft ? t('workflow.versionHistory.currentDraft') : item.marked_name || t('workflow.versionHistory.defaultName')}
  90. </div>
  91. {isLatest && (
  92. <div className='flex items-center shrink-0 h-5 px-[5px] rounded-md border border-text-accent-secondary
  93. bg-components-badge-bg-dimm text-text-accent-secondary system-2xs-medium-uppercase'>
  94. {t('workflow.versionHistory.latest')}
  95. </div>
  96. )}
  97. </div>
  98. {
  99. !isDraft && (
  100. <div className='text-text-secondary system-xs-regular break-words'>
  101. {item.marked_comment || ''}
  102. </div>
  103. )
  104. }
  105. {
  106. !isDraft && (
  107. <div className='text-text-tertiary system-xs-regular truncate'>
  108. {`${formatTime(item.created_at)} · ${item.created_by.name}`}
  109. </div>
  110. )
  111. }
  112. </div>
  113. {/* Context Menu */}
  114. {!isDraft && isHovering && (
  115. <div className='absolute right-1 top-1'>
  116. <ContextMenu
  117. isShowDelete={!isLatest}
  118. isNamedVersion={!!item.marked_name}
  119. open={open}
  120. setOpen={setOpen}
  121. handleClickMenuItem={handleClickMenuItem}
  122. />
  123. </div>
  124. )}
  125. </div>
  126. )
  127. }
  128. export default React.memo(VersionHistoryItem)