FileExplorerAndEditorRow.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import { IS_PLATFORM } from 'common'
  2. import { Edit, File, Trash } from 'lucide-react'
  3. import {
  4. cn,
  5. ContextMenu,
  6. ContextMenuContent,
  7. ContextMenuItem,
  8. ContextMenuSeparator,
  9. ContextMenuTrigger,
  10. Tooltip,
  11. TooltipContent,
  12. TooltipTrigger,
  13. TreeViewItem,
  14. type INodeRendererProps,
  15. } from 'ui'
  16. import { FileData } from './FileExplorerAndEditor.types'
  17. type FileExplorerAndEditorRowProps = INodeRendererProps & {
  18. files: FileData[]
  19. selectedFileId: number
  20. setSelectedFileId: (id: number) => void
  21. handleFileNameChange: (id: number, newName: string) => void
  22. handleStartRename: (id: number) => void
  23. handleFileDelete: (id: number) => void
  24. }
  25. export const FileExplorerAndEditorRow = ({
  26. element,
  27. isBranch,
  28. isExpanded,
  29. getNodeProps,
  30. level,
  31. files,
  32. selectedFileId,
  33. setSelectedFileId,
  34. handleFileNameChange,
  35. handleStartRename,
  36. handleFileDelete,
  37. }: FileExplorerAndEditorRowProps) => {
  38. const nodeProps = getNodeProps()
  39. const originalId =
  40. typeof element.metadata?.originalId === 'number' ? element.metadata.originalId : null
  41. const state = element.metadata?.state as FileData['state']
  42. const isEditing = Boolean(element.metadata?.isEditing)
  43. return (
  44. <ContextMenu modal={false}>
  45. <ContextMenuTrigger asChild>
  46. <div>
  47. <TreeViewItem
  48. {...nodeProps}
  49. isExpanded={isExpanded}
  50. isBranch={isBranch}
  51. isSelected={files.find((f) => f.id === originalId)?.id === selectedFileId}
  52. level={level}
  53. xPadding={16}
  54. name={element.name}
  55. className={cn(
  56. isEditing
  57. ? ''
  58. : state === 'new'
  59. ? 'text-brand-600'
  60. : state === 'modified'
  61. ? 'text-code_block-2'
  62. : ''
  63. )}
  64. icon={<File size={14} className="text-foreground-light shrink-0" />}
  65. isEditing={isEditing}
  66. onEditSubmit={(value) => {
  67. if (IS_PLATFORM && originalId !== null) {
  68. handleFileNameChange(originalId, value)
  69. }
  70. }}
  71. onClick={() => {
  72. if (originalId !== null && !isEditing) {
  73. setSelectedFileId(originalId)
  74. }
  75. }}
  76. onDoubleClick={() => {
  77. if (IS_PLATFORM && originalId !== null) {
  78. handleStartRename(originalId)
  79. }
  80. }}
  81. actions={
  82. state !== 'unchanged' && (
  83. <div className="flex items-center justify-center w-3">
  84. <Tooltip>
  85. <TooltipTrigger className="text-xs">
  86. {state === 'new' ? 'U' : 'M'}
  87. </TooltipTrigger>
  88. <TooltipContent side="bottom">
  89. {state === 'new' ? 'Unsaved' : 'Modified'}
  90. </TooltipContent>
  91. </Tooltip>
  92. </div>
  93. )
  94. }
  95. />
  96. </div>
  97. </ContextMenuTrigger>
  98. {IS_PLATFORM && (
  99. <ContextMenuContent onCloseAutoFocus={(e) => e.stopPropagation()}>
  100. <ContextMenuItem
  101. className="gap-x-2"
  102. onSelect={() => {
  103. if (originalId !== null) handleStartRename(originalId)
  104. }}
  105. onFocusCapture={(e) => e.stopPropagation()}
  106. >
  107. <Edit size={14} />
  108. Rename file
  109. </ContextMenuItem>
  110. {files.length > 1 && (
  111. <>
  112. <ContextMenuSeparator />
  113. <ContextMenuItem
  114. className="gap-x-2"
  115. onSelect={() => {
  116. if (originalId !== null) {
  117. handleFileDelete(originalId)
  118. }
  119. }}
  120. onFocusCapture={(e) => e.stopPropagation()}
  121. >
  122. <Trash size={14} />
  123. Delete file
  124. </ContextMenuItem>
  125. </>
  126. )}
  127. </ContextMenuContent>
  128. )}
  129. </ContextMenu>
  130. )
  131. }